fix blocking during scans

This commit is contained in:
Garret Patti
2026-04-20 09:11:14 -04:00
parent a6d657d87d
commit cedc012733
5 changed files with 346 additions and 109 deletions

View File

@@ -3,7 +3,8 @@ import crypto from 'crypto'
import type { Library, ImportedTag, TagMapping } from '@/types'
import { getDb } from './db'
import { resolveLibraryRoot } from './libraries'
import { parseComicInfo } from './comic-info'
import { parseComicInfoAsync } from './comic-info'
import { mapConcurrent } from './zip-utils'
// ─── Metadata Import ──────────────────────────────────────────────────────────
@@ -13,7 +14,7 @@ import { parseComicInfo } from './comic-info'
* - For each tag: if a mapping exists, assigns the real tag; otherwise creates
* an imported tag entry.
*/
export function importComicMetadata(library: Library): void {
export async function importComicMetadata(library: Library): Promise<void> {
const db = getDb()
const libraryRoot = resolveLibraryRoot(library)
@@ -56,53 +57,65 @@ export function importComicMetadata(library: Library): void {
let importedCount = 0
db.transaction(() => {
for (const issue of issues) {
const absPath = path.join(libraryRoot, issue.file_path)
const info = parseComicInfo(absPath)
if (!info) continue
// Process in batches: async file reads (10 concurrent) followed by batch DB writes,
// with an event-loop yield between batches to keep the app responsive.
const BATCH_SIZE = 50
for (let i = 0; i < issues.length; i += BATCH_SIZE) {
const batch = issues.slice(i, i + BATCH_SIZE)
// Merge with existing metadata JSON (preserve pageCount, coverUrl, etc.)
const existingMeta = issue.metadata ? JSON.parse(issue.metadata) : {}
const mergedMeta = {
...existingMeta,
writer: info.writer,
publisher: info.publisher,
translator: info.translator,
web: info.web,
month: info.month,
day: info.day,
}
// Async: read ComicInfo.xml from each archive concurrently (10 at a time).
// Uses async ZIP central-directory reader — no full-file reads.
const infos = await mapConcurrent(batch, 10, (issue) =>
parseComicInfoAsync(path.join(libraryRoot, issue.file_path))
)
updateItem.run({
item_key: issue.item_key,
title: info.title ?? existingMeta.title ?? null,
year: info.year,
genres: info.genre,
metadata: JSON.stringify(mergedMeta),
})
// Sync: write this batch to the DB in one transaction.
db.transaction(() => {
for (let j = 0; j < batch.length; j++) {
const issue = batch[j]
const info = infos[j]
if (!info) continue
// Process tags
for (const tagName of info.tags) {
const mappedTagId = mappings.get(tagName)
if (mappedTagId) {
// Mapping exists — assign the real tag
addMediaTag.run(issue.item_key, mappedTagId)
} else {
// No mapping — create imported tag
const importedTagId = crypto.randomUUID()
const row = upsertImportedTag.get({
id: importedTagId,
library_id: library.id,
name: tagName,
}) as { id: string }
addItemImportedTag.run(issue.item_key, row.id)
const existingMeta = issue.metadata ? JSON.parse(issue.metadata) : {}
const mergedMeta = {
...existingMeta,
writer: info.writer,
publisher: info.publisher,
translator: info.translator,
web: info.web,
month: info.month,
day: info.day,
}
}
importedCount++
}
})()
updateItem.run({
item_key: issue.item_key,
title: info.title ?? existingMeta.title ?? null,
year: info.year,
genres: info.genre,
metadata: JSON.stringify(mergedMeta),
})
for (const tagName of info.tags) {
const mappedTagId = mappings.get(tagName)
if (mappedTagId) {
addMediaTag.run(issue.item_key, mappedTagId)
} else {
const importedTagId = crypto.randomUUID()
const row = upsertImportedTag.get({
id: importedTagId,
library_id: library.id,
name: tagName,
}) as { id: string }
addItemImportedTag.run(issue.item_key, row.id)
}
}
importedCount++
}
})()
await new Promise<void>((r) => setImmediate(r))
}
console.log(`[comic-metadata] Imported metadata for ${importedCount}/${issues.length} issues in "${library.name}"`)
}