mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
feat(metadata): write metadata to every file of a multi-file book
Books often carry several files, such as an EPUB beside a PDF or an audiobook split into tracks. Write-back reached only the primary file and the audio tracks, so the other formats kept stale metadata. Add an opt-in per-library mode that writes every content file of a book. Target selection, capability reporting and the Files tab now share one role-aware selector, covers resolve per medium, and results report per-format counts. Read-along EPUBs get their own toggle, off by default, and their own size limit of 1000 MB, so they no longer follow the EPUB settings. Live testing on copies of real libraries surfaced write-path bugs, fixed here as well: - tag-only writes to chaptered M4B files failed because FFmpeg copied the chapter data track - an undecodable cover failed every file instead of falling back to metadata only - failure reasons stored the FFmpeg command line and absolute paths; they are now sanitized - size limits used the stored size, so an unknown size bypassed them; writes now check the disk - atomic replace dropped the file mode and ownership - the EPUB temp name failed on long file names and could clash with a user file - tracks in disc folders got duplicate titles - a PDF worker that exited without a result hung its write forever; it now fails and times out - null on a NOT NULL library setting returned 500 instead of 400 Generates migrations 0104 and 0105. Every new API field is optional, and released iOS builds decode none of them. Ref #694
This commit is contained in:
@@ -263,11 +263,27 @@ export type ReadAloudProgressSync = {
|
||||
export type BookFileWriteDisabledReason =
|
||||
"library_disabled" | "no_primary_file" | "format_not_supported" | "format_disabled" | "file_exceeds_size_limit";
|
||||
|
||||
/** Why one file of a book is not written. Per-file only: never reported as the book-level `reason`. */
|
||||
export type BookFileWriteTargetSkipReason = "format_not_supported" | "format_disabled" | "file_exceeds_size_limit" | "not_content_file";
|
||||
|
||||
/** One file the book's write-back considered, with the fields its own format can hold. */
|
||||
export type BookFileWriteTargetStatus = {
|
||||
fileId: number;
|
||||
format: string | null;
|
||||
writable: boolean;
|
||||
reason: BookFileWriteTargetSkipReason | null;
|
||||
writableFields: BookFileWriteField[];
|
||||
};
|
||||
|
||||
export type BookFileWriteStatus = {
|
||||
enabled: boolean;
|
||||
reason: BookFileWriteDisabledReason | null;
|
||||
/** Union across writable files. Kept for released clients; prefer `targets` for anything per file. */
|
||||
writableFormats: BookFormat[];
|
||||
/** Union across writable files. Kept for released clients; prefer `targets` for anything per file. */
|
||||
writableFields: BookFileWriteField[];
|
||||
/** Every file write-back considered, in write order. Absent from servers that predate it. */
|
||||
targets?: BookFileWriteTargetStatus[];
|
||||
};
|
||||
|
||||
export type BookDetail = {
|
||||
|
||||
@@ -187,11 +187,20 @@ export function getBookFileWriteFormatFields(format: string | null | undefined):
|
||||
return BOOK_FILE_WRITE_FORMAT_FIELDS[key as keyof typeof BOOK_FILE_WRITE_FORMAT_FIELDS] ?? [];
|
||||
}
|
||||
|
||||
export type WriteResultFileCounts = {
|
||||
processed: number;
|
||||
succeeded: number;
|
||||
failed: number;
|
||||
skipped: number;
|
||||
};
|
||||
|
||||
export type WriteResult = {
|
||||
status: "success" | "skipped" | "failed";
|
||||
fieldsWritten: string[];
|
||||
durationMs: number;
|
||||
reason?: string;
|
||||
/** Per-file outcome counts, present when a book wrote to more than one file. `status` stays the aggregate. */
|
||||
fileCounts?: WriteResultFileCounts;
|
||||
};
|
||||
|
||||
export type LibraryFileSyncProgressEvent =
|
||||
|
||||
@@ -80,6 +80,11 @@ export interface Library {
|
||||
fileWriteKindleMaxFileSizeMb: number;
|
||||
fileWriteAudioEnabled: boolean;
|
||||
fileWriteAudioMaxFileSizeMb: number;
|
||||
/** Write metadata into every content file of a book, not just its primary file or audio tracks. */
|
||||
fileWriteAllFiles: boolean;
|
||||
/** EPUBs with synced narration have their own toggle and limit: they embed audio and run large. */
|
||||
fileWriteReadAlongEnabled: boolean;
|
||||
fileWriteReadAlongMaxFileSizeMb: number;
|
||||
fileRenameEnabled: boolean;
|
||||
folders: LibraryFolder[];
|
||||
bookCount?: number;
|
||||
|
||||
Reference in New Issue
Block a user