comic library improvements

This commit is contained in:
Garret Patti
2026-04-20 21:42:23 -04:00
parent bd028a7a5d
commit 27430dbf52
5 changed files with 312 additions and 67 deletions

View File

@@ -552,6 +552,36 @@ async function scanComics(library: Library, libraryRoot: string): Promise<void>
const db = getDb()
const now = Date.now()
// Save ComicInfo metadata for issues that were already imported so we can
// restore it after the clear+upsert without re-reading any CBZ files.
type SavedInfo = { title: string | null; year: number | null; genres: string | null; comicFields: Record<string, unknown> }
const savedComicInfo = new Map<string, SavedInfo>()
{
const rows = db
.prepare(
`SELECT item_key, title, year, genres, metadata FROM media_items
WHERE library_id = ? AND item_type = 'comic_issue'
AND (year IS NOT NULL OR genres IS NOT NULL)`
)
.all(library.id) as { item_key: string; title: string | null; year: number | null; genres: string | null; metadata: string | null }[]
for (const row of rows) {
const meta: Record<string, unknown> = row.metadata ? (JSON.parse(row.metadata) as Record<string, unknown>) : {}
savedComicInfo.set(row.item_key, {
title: row.title,
year: row.year,
genres: row.genres,
comicFields: {
writer: meta.writer,
publisher: meta.publisher,
translator: meta.translator,
web: meta.web,
month: meta.month,
day: meta.day,
},
})
}
}
clearLibraryItems(db, library.id)
const upsertSeries = db.prepare(`
@@ -648,6 +678,18 @@ async function scanComics(library: Library, libraryRoot: string): Promise<void>
}
}
// Build a map of item_key → fresh scan metadata (needed for ComicInfo restore below).
const freshMetaMap = new Map<string, Record<string, unknown>>()
for (const entry of allRecords) {
if (entry.type === 'issue') {
const rec = entry.rec as { item_key: unknown; metadata: unknown }
freshMetaMap.set(
String(rec.item_key),
JSON.parse(String(rec.metadata)) as Record<string, unknown>
)
}
}
// Insert in batches of 500, yielding the event loop between batches so the app
// remains responsive to HTTP requests during a large scan.
const BATCH_SIZE = 500
@@ -662,6 +704,23 @@ async function scanComics(library: Library, libraryRoot: string): Promise<void>
await new Promise<void>((r) => setImmediate(r))
}
// Restore previously-imported ComicInfo data for issues that still exist on disk.
// Merges scan-derived fields (pageCount, coverUrl) with the saved ComicInfo fields
// so neither set of data is lost. Title from ComicInfo is also preserved.
if (savedComicInfo.size > 0) {
const restoreStmt = db.prepare(
'UPDATE media_items SET title = @title, year = @year, genres = @genres, metadata = @metadata WHERE item_key = @item_key'
)
db.transaction(() => {
for (const [item_key, saved] of savedComicInfo) {
const freshMeta = freshMetaMap.get(item_key)
if (!freshMeta) continue // file was removed from disk
const merged = { ...freshMeta, ...saved.comicFields }
restoreStmt.run({ item_key, title: saved.title, year: saved.year, genres: saved.genres, metadata: JSON.stringify(merged) })
}
})()
}
// Prewarm CBZ cover thumbnails — fire-and-forget so they don't block scan completion.
for (const item of items) {
const issuesToWarm: ComicIssue[] = 'issues' in item