mymusics

retro MySpace-style music player
Log | Files | Refs | README

commit b27126269546093439cf1b93f593f8f1cc31cd42
parent 751272017c9e579f763b8c07d63f3f617449ec92
Author: Pablo Murad <pblmrd@gmail.com>
Date:   Fri,  1 May 2026 12:12:16 -0300

metadata

Diffstat:
MREADME.md | 2+-
Mserver/index.ts | 38++++++++++++++++++++++++++++++++++----
Msrc/App.css | 25+++++++++++++++++++++++++
Msrc/App.tsx | 17+++++++++++++++++
4 files changed, 77 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md @@ -91,7 +91,7 @@ The API returns **503** only when the in-memory track pool is **empty** (`trackC Check `metadataTsv`, `metadataExists`, `metadataSizeBytes`, `trackCount`, `tracksReady`, and `hint`. -2. **Align `METADATA_TSV`** with the real file (e.g. `/opt/mymusics/data/metadata.tsv`). Wrong paths such as `/opt/data/metadata.tsv` look “almost right” but **fail** if the file lives under the app directory. Alternatively **remove** `METADATA_TSV` from `.env` / PM2 so the app uses the default `data/metadata.tsv` next to the project. +2. **Align `METADATA_TSV`** with the real file (e.g. `/opt/mymusics/data/metadata.tsv`). Wrong paths such as `/opt/data/metadata.tsv` look “almost right” but **fail** if the file lives under the app directory. Alternatively **remove** `METADATA_TSV` from `.env` / PM2 so the app uses the default `data/metadata.tsv` next to the project. If the env path is missing but **`data/metadata.tsv` exists inside the app**, the server **falls back** to that file automatically and logs a warning (you should still fix `.env` to avoid confusion). 3. **Restart the process** after editing env so variables reload: diff --git a/server/index.ts b/server/index.ts @@ -25,9 +25,34 @@ function resolvePath(p: string): string { const PORT = resolveApiPort(process.env); /** Resolved path to data/metadata.tsv next to the app (independent of METADATA_TSV env). */ const BUNDLED_METADATA_TSV = path.join(__dirname, "..", "data", "metadata.tsv"); -const METADATA_TSV = process.env.METADATA_TSV - ? resolvePath(process.env.METADATA_TSV.trim()) - : BUNDLED_METADATA_TSV; + +/** If METADATA_TSV points to a missing file but the bundled copy exists, use bundled (VPS typo self-heal). */ +function resolveEffectiveMetadataTsv(): { + path: string; + envRequested: string | null; + usedFallback: boolean; +} { + const raw = process.env.METADATA_TSV?.trim(); + if (!raw) { + return { path: BUNDLED_METADATA_TSV, envRequested: null, usedFallback: false }; + } + const resolved = resolvePath(raw); + if (fs.existsSync(resolved)) { + return { path: resolved, envRequested: resolved, usedFallback: false }; + } + if (fs.existsSync(BUNDLED_METADATA_TSV)) { + console.warn( + `MyMusics: METADATA_TSV not found at ${resolved}; using bundled ${BUNDLED_METADATA_TSV}`, + ); + return { path: BUNDLED_METADATA_TSV, envRequested: resolved, usedFallback: true }; + } + return { path: resolved, envRequested: resolved, usedFallback: false }; +} + +const _meta = resolveEffectiveMetadataTsv(); +const METADATA_TSV = _meta.path; +const METADATA_ENV_REQUESTED = _meta.envRequested; +const METADATA_USED_FALLBACK = _meta.usedFallback; function hintForMetadataNotFound(message: string): string { if (!message.includes("not found")) return message; @@ -91,7 +116,9 @@ function randomTrack(): TrackMeta | null { async function main() { console.info(`MyMusics: cwd=${process.cwd()}`); - console.info(`MyMusics: METADATA_TSV resolved to ${METADATA_TSV}`); + console.info( + `MyMusics: metadata file ${METADATA_TSV}${METADATA_USED_FALLBACK ? ` (fallback; env had ${METADATA_ENV_REQUESTED})` : ""}`, + ); try { rebuildPool(); } catch (e) { @@ -116,6 +143,9 @@ async function main() { trackCount: pool.length, tracksReady: pool.length > 0, metadataTsv: METADATA_TSV, + ...(METADATA_ENV_REQUESTED && METADATA_ENV_REQUESTED !== METADATA_TSV + ? { metadataEnvRequested: METADATA_ENV_REQUESTED, metadataUsedFallback: METADATA_USED_FALLBACK } + : {}), metadataExists: fs.existsSync(METADATA_TSV), metadataSizeBytes, cwd: process.cwd(), diff --git a/src/App.css b/src/App.css @@ -199,6 +199,31 @@ color: var(--sky); } +.health-info { + margin-bottom: 1.25rem; + padding: 0.85rem 1rem; + border-radius: 12px; + border: 1px solid color-mix(in srgb, var(--sky) 40%, transparent); + background: color-mix(in srgb, var(--sky) 8%, var(--panel)); +} + +.health-info strong { + display: block; + margin-bottom: 0.35rem; + color: var(--sky); + font-size: 0.85rem; + letter-spacing: 0.03em; + text-transform: uppercase; +} + +.health-info p { + margin: 0; + font-size: 0.85rem; + line-height: 1.45; + color: var(--muted); + word-break: break-word; +} + .footer { margin-top: 2.5rem; text-align: center; diff --git a/src/App.tsx b/src/App.tsx @@ -18,6 +18,8 @@ type HealthBody = { tracksReady?: boolean; hint?: string; metadataTsv?: string; + metadataEnvRequested?: string; + metadataUsedFallback?: boolean; metadataExists?: boolean; trackCount?: number; }; @@ -29,6 +31,7 @@ export default function App() { const [history, setHistory] = useState<TrackInfo[]>([]); const [autoPlay, setAutoPlay] = useState(true); const [healthWarn, setHealthWarn] = useState<string | null>(null); + const [healthInfo, setHealthInfo] = useState<string | null>(null); const playUrl = useCallback((url: string) => { const a = audioRef.current; @@ -75,6 +78,7 @@ export default function App() { const res = await fetch("/api/health"); const h = (await res.json()) as HealthBody; if (!h.tracksReady) { + setHealthInfo(null); const parts = [ h.hint, h.metadataTsv && `Path: ${h.metadataTsv}`, @@ -88,6 +92,13 @@ export default function App() { ); } else { setHealthWarn(null); + if (h.metadataUsedFallback && h.metadataEnvRequested && h.metadataTsv) { + setHealthInfo( + `METADATA_TSV was not found at ${h.metadataEnvRequested}. Using bundled file at ${h.metadataTsv}. Remove or fix METADATA_TSV in .env / PM2.`, + ); + } else { + setHealthInfo(null); + } } } catch { setHealthWarn(null); @@ -117,6 +128,12 @@ export default function App() { </p> </div> ) : null} + {healthInfo ? ( + <div className="health-info" role="status"> + <strong>Metadata path</strong> + <p>{healthInfo}</p> + </div> + ) : null} <header className="header"> <img className="logo"