feat(metadata): write-to-file — embedded EPUB OPF + Calibre sidecars
Phase 5a of the metadata-writer feature: the book editor gains a "Write to file" action (admin) that persists the item's CURRENT database metadata into the artifact on disk, so the library stays portable to Calibre and other apps — the thing Save alone can't do. Dispatch (services/metadata_writer.go): EPUB/KEPUB gets its embedded OPF spliced in place (fresh dc:* block generated inside the ORIGINAL metadata open tag; manifest/spine/guide untouched; every other zip entry copied byte-identical in original order so mimetype stays first and STORED; original dc:identifiers preserved — the package's unique-identifier must keep resolving). Everything else takes a Calibre-style sidecar: metadata.opf when the book is ALONE in its directory (Calibre's own convention — round-trips through Calibre too), <basename>.opf when siblings share the folder, so one book's sidecar can never poison dir-mates' scans (the scanner now reads both, basename first). Safety contract: temp file + atomic rename — the original is never truncated on failure; the generated OPF is unit-tested to ROUND-TRIP through the scanner's own parser (write side and read side can never disagree); metadata_overrides are deliberately kept (file and DB agree after the write; overrides still guard against stale sidecars). The endpoint re-extracts inline (RescanMediaItem) and reports rescan failure separately — a failed rescan doesn't undo the write. SHA-256 changes are inherent and documented for users (Kobo/KOReader re-key). UI: the metadata editor modal carries the file path as a data attribute; the button confirms before mutating, labels the target (embedded vs sidecar) and toasts the outcome. Integration tests pin the 403, missing-file error, EPUB embed + post-write rescan title, the lone-book metadata.opf case, and the shared-dir <basename>.opf case with no dir-scoped litter.
This commit is contained in:
1 parent
61e5f47ec0
commit
eaf1b2c8dd
13 files changed
+1591
-413
No files matched your search
@@ -4166,6 +4166,18 @@ SELECT COUNT(*)::int FROM (
|
||||
)
|
||||
) groups;
|
||||
|
||||
-- name: CountOtherMediaItemsInDir :one
|
||||
-- Sibling-book count for sidecar naming (MetadataWriter): a dir-scoped
|
||||
-- metadata.opf may only be written when NO other non-archived item
|
||||
-- shares the directory; otherwise the sidecar goes to
|
||||
-- <basename>.opf so one book's metadata can never poison dir-mates.
|
||||
SELECT COUNT(*)::int
|
||||
FROM media_items
|
||||
WHERE library_id = sqlc.narg('library_id')::uuid
|
||||
AND id != sqlc.narg('id')::uuid
|
||||
AND archived_at IS NULL
|
||||
AND file_path LIKE (sqlc.narg('dir_prefix')::text || '%');
|
||||
|
||||
-- name: GetSeriesCovers :many
|
||||
SELECT cover_image_path, library_id
|
||||
FROM media_items
|
||||
|
||||
Reference in new issue
Block a user