mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
feat(tmdb): metadata cache panel with a clear button in settings (#1244)
* feat(tmdb): metadata cache panel with a clear button in settings Adds "Metadata cache — N entries · X MB" with a Clear button to Settings > Metadata (TMDB), next to the API key it belongs to. Three things it is good for: dropping stale or wrong metadata so the next open refetches it, seeing what the cache actually costs on disk, and reclaiming rows that a lookup-key version bump has orphaned — a bump makes rows unreachable, not deleted, so nothing else would ever collect them. Sizing the cache is a full table scan (LENGTH() on TEXT counts characters, so the SUM casts to BLOB to get bytes), which is why stats load lazily and only once the TMDB section is the active one rather than on every settings open. Clearing is always safe: enrichment refetches on demand, so the only cost is the next few requests. Works in both environments — the PWA has no bridge, so the service reports and clears its session-scoped in-memory map instead. i18n: 4 keys across all 19 locales via the tools/i18n workflow; placeholder integrity verified. Contract fixtures updated for both the preload bridge and the DB-worker payload shapes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tmdb): make cache clearing durable and stop reporting failures as empty Four review findings, all real: - A metadata write already in flight when the user cleared would land afterwards and silently restore what they removed. Writes now carry the generation they started in; a write that outlives a clear is dropped (PWA) or undone (Electron). - The PWA byte count used String.length, i.e. UTF-16 code units, so localized payloads under-reported and disagreed with the SQLite BLOB byte count. TextEncoder now measures actual bytes. - A failed stats read returned a valid zero-entry result, so the panel claimed an empty cache and disabled Clear while rows were still there. getStats/clear now return null on failure and the panel says so instead of inventing state. - No behavioural coverage existed for either side. Tests: SQL ops (entry/byte reporting, empty table, missing row, delete count) and the service (encoded bytes, clear count, and a write racing a clear). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tmdb): make the cache clear precise and version skew visible Review follow-ups on the cache panel: - A write that was in flight when the user cleared used to trigger a second full-table clear once it landed, which also deleted anything written in between. clear() now waits for the writes issued before it and lets the single clear take them; later writes survive. - An Electron shell without the maintenance ops fell through to the renderer map, which is always empty there — it reported an empty cache and disabled the Clear button while SQLite was full. Both operations now report unsupported instead. - Component coverage for the panel (deferred scan, clear + re-read, failed clear, failed read) and Electron-path service coverage. - The canonical IPC and settings sections of the enrichment doc, plus the matching CLAUDE.md lines, now list the maintenance ops. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tmdb): drop Promise.allSettled from the cache clear The web target compiles against lib es2018, so allSettled broke the Windows frontend build (TS2550). The pending writes swallow their own errors, so a plain Promise.all over neutralized promises does the job. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tmdb): keep a synchronous bridge throw inside the cache write Moving the write into a tracked promise dropped the try/catch that used to cover the call itself, so a bridge that threw synchronously would escape set(). Wrap it in an async IIFE, which turns that back into a rejection the same handler swallows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tmdb): retry the cache size read when the section is reopened The effect skipped the read once cacheError was set, so one transient IPC failure left the panel showing "could not read the cache" for the life of the settings page — and the only enabled control that could shift it was the destructive Clear button. Gate on the stats signal alone: reopening the section retries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tmdb): queue writes that start while the cache is being cleared Awaiting the in-flight writes closed one side of the race and left the other open: a set() that started during that wait dispatched its IPC immediately, was absent from the snapshot, and could reach SQLite just before the delete — so a row written after the user clicked Clear was removed anyway. clear() now holds its own promise for the whole operation and set() waits on it, which puts such a write on the far side of the delete. Rows are stamped when they are dispatched rather than when set() was called, since a write may have waited. Covered by a test that fails without the guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(tmdb): add the release note for the cache panel Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(tmdb): cover the cache panel with an Electron E2E The panel drives IPC and SQLite, and nothing exercised that path end to end. The new test seeds a row through the preload bridge — enrichment itself needs a TMDB key that CI does not have — then opens the section, asserts the reported size, clears, and reads the database back to confirm the row is gone rather than merely hidden. Verified both ways: dropping the DELETE from clearTmdbMetadata fails it. Settings nav buttons gained a data-test-id so the section can be opened without matching translated labels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
d76b2d2a57
commit
0b967d66d4
39 files changed
+888
-24
No files matched your search
@@ -75,8 +75,15 @@ also show a production-status chip (`tmdb_status`): TMDB returns `status`
|
||||
as an ENGLISH string regardless of the request language, so it is
|
||||
normalized to a token (`normalizeSeriesStatus`) and rendered through
|
||||
translated labels (`seriesStatusLabelKey`) — the raw value never reaches
|
||||
the UI. Unknown values are dropped rather than displayed. A "check key" button
|
||||
in the settings section validates the API key against `/configuration`.
|
||||
the UI. Unknown values are dropped rather than displayed. A "check key"
|
||||
button in the settings section validates the API key against
|
||||
`/configuration`, and a cache panel beside it shows the stored row count
|
||||
plus payload size and can drop the lot (`DB_GET_TMDB_CACHE_STATS` /
|
||||
`DB_CLEAR_TMDB_METADATA`, in-memory map in the PWA). Sizing is a full
|
||||
scan, so it only runs once the TMDB section is the active one. Clearing
|
||||
costs nothing but the next few requests — enrichment refetches on demand
|
||||
— and it is also the escape hatch when a lookup-key version bump orphans
|
||||
rows, since a bump makes them unreachable rather than deleting them.
|
||||
|
||||
## Match Confidence
|
||||
|
||||
@@ -303,12 +310,31 @@ person cache rows are unaffected.
|
||||
Electron IPC path (follows the standard DB worker contract, see
|
||||
[SQLite DB Worker](./sqlite-db-worker.md)):
|
||||
|
||||
- Worker ops: `DB_GET_TMDB_METADATA`, `DB_SET_TMDB_METADATA`
|
||||
- Worker ops: `DB_GET_TMDB_METADATA`, `DB_SET_TMDB_METADATA`, plus the two
|
||||
maintenance ops behind the settings cache panel,
|
||||
`DB_GET_TMDB_CACHE_STATS` and `DB_CLEAR_TMDB_METADATA`
|
||||
(`database-worker.types.ts`, `database.worker.ts`,
|
||||
`operations/tmdb.operations.ts`)
|
||||
- IPC registration: `events/database/tmdb.events.ts`
|
||||
- Preload bridge: `dbGetTmdbMetadata` / `dbSetTmdbMetadata` on
|
||||
`window.electron` (typed in `ElectronBridgeApi`)
|
||||
- Preload bridge: `dbGetTmdbMetadata` / `dbSetTmdbMetadata` /
|
||||
`dbGetTmdbCacheStats` / `dbClearTmdbMetadata` on `window.electron` (typed
|
||||
in `ElectronBridgeApi`)
|
||||
|
||||
`TmdbCacheService` treats the maintenance pair as optional on the bridge: an
|
||||
Electron shell that predates them reports `null` (unsupported) rather than
|
||||
falling back to the renderer map, which is always empty in Electron and
|
||||
would claim the SQLite cache is empty. A `clear()` first awaits the writes
|
||||
already in flight so they land and are deleted with everything else, and
|
||||
holds the clear promise for its duration so a write starting meanwhile
|
||||
queues behind the delete instead of racing it. Rows written after the user
|
||||
cleared are deliberately kept.
|
||||
|
||||
The panel's full path — settings button, preload bridge, DB worker, real
|
||||
SQLite — is covered by `@settings @electron @persistence sizes and clears
|
||||
the TMDB metadata cache` in `apps/electron-backend-e2e/src/settings.e2e.ts`.
|
||||
It seeds a row through `dbSetTmdbMetadata` rather than through enrichment,
|
||||
which needs an API key that builds outside the release pipeline do not
|
||||
carry.
|
||||
|
||||
The PWA uses a session-scoped in-memory map (acceptable for phase 1; TMDB
|
||||
supports CORS so the PWA calls the API directly).
|
||||
@@ -317,7 +343,12 @@ supports CORS so the PWA calls the API directly).
|
||||
|
||||
`Settings.tmdb?: { enabled: boolean; apiKey?: string }`
|
||||
(`libs/shared/interfaces/src/lib/tmdb.interface.ts`). The settings page has
|
||||
a "Metadata (TMDB)" section (enable toggle + optional API key override).
|
||||
a "Metadata (TMDB)" section: enable toggle, optional API key override with a
|
||||
"check key" button (validates against `/configuration`), and a cache panel
|
||||
showing the stored row count plus payload size with a button that drops the
|
||||
lot. Sizing is a full table scan, so it runs only once that section is the
|
||||
active one, and a failed read or clear says so instead of showing an empty
|
||||
cache.
|
||||
|
||||
The embedded default key lives in `DEFAULT_TMDB_API_KEY`
|
||||
(`libs/services/src/lib/tmdb/tmdb-config.ts`) and is an **empty placeholder
|
||||
|
||||
Reference in new issue
Block a user