feat(metadata): per-field user overrides that survive library rescans
Previously both the metadata editor (PUT /api/media-items/:id) and the
scanner (library scans, force rescans, per-book rescans) wrote through
the same unconditional UPDATE media_items query, so any rescan wiped
user-written descriptions, tags, and uploaded covers. Custom and scanned
values were indistinguishable, and custom cover uploads even wrote to
the same {file}.cover.jpg sidecar path the scanner generates, so each
side silently clobbered the other.
Introduce metadata_overrides, a TEXT[] column on media_items listing the
column names the user has customized:
- Saving metadata records overrides per field by diffing the submitted
values against the stored row (an untouched save records nothing);
overrides accumulate until an explicit reset. Cover upload/removal
always marks cover_image_path. Bulk updates mark each applied field.
- The scanner merges: updateMediaItem() now takes the existing row and
restores every overridden column (including derived *_search arrays)
before writing, and preserves the override set itself.
- Uploaded covers move to a dedicated {file}.custom_cover.{jpg|png|webp}
sidecar so the scanner can never overwrite a user cover on disk.
- RescanMediaItem gains resetOverrides: POST /api/media-items/:id/rescan?
reset_overrides=true clears the set first, returning the item to pure
scanned defaults.
Shared detection/restore helpers live in internal/utils
(metadata_overrides.go) with unit tests covering detection, accumulation,
unset-form equality, and restore-with-derived-fields. Schema change is
an idempotent ADD COLUMN IF NOT EXISTS applied on startup. Also includes
incidental gofmt of NewMediaScanner literals in media_scanner.go.
This commit is contained in:
@@ -207,6 +207,12 @@ CREATE TABLE IF NOT EXISTS media_items (
|
||||
-- Summary (distinct from description - may merge with Calibre description)
|
||||
summary TEXT, -- Summary from ComicInfo.xml (may be merged with description from Calibre)
|
||||
|
||||
-- Per-field user-override tracking. Column names listed here (e.g.
|
||||
-- 'description', 'tags', 'cover_image_path') are user-customized and MUST
|
||||
-- NOT be overwritten by library scans or per-book rescans; only the
|
||||
-- reset-to-scanned-defaults action clears them.
|
||||
metadata_overrides TEXT[] NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Chapter metadata for reader navigation and progress tracking
|
||||
-- Caches detected chapter structure to avoid re-parsing files
|
||||
-- Populated by ReaderService.DetectChapters() on first read
|
||||
@@ -232,6 +238,11 @@ CREATE INDEX IF NOT EXISTS idx_media_items_contributors_gin ON media_items USING
|
||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS tags_search TEXT[];
|
||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS contributors_search TEXT[];
|
||||
|
||||
-- Per-field user-override tracking (idempotent backfill for existing
|
||||
-- installs; the column is also declared in the media_items table
|
||||
-- definition above for fresh databases)
|
||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS metadata_overrides TEXT[] NOT NULL DEFAULT '{}';
|
||||
|
||||
-- Create GIN indexes for fast search field searches
|
||||
CREATE INDEX IF NOT EXISTS idx_media_items_tags_search ON media_items USING GIN (tags_search);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_items_contributors_search ON media_items USING GIN (contributors_search);
|
||||
|
||||
Reference in New Issue
Block a user