fix blocking during scans
This commit is contained in:
@@ -4,6 +4,7 @@ import AdmZip from 'adm-zip'
|
||||
import type { ComicIssue, ComicSeries } from '@/types'
|
||||
import { getDb } from './db'
|
||||
import { HIDDEN_FILES, thumbnailApiUrl } from './media-utils'
|
||||
import { countZipImages, mapConcurrent } from './zip-utils'
|
||||
|
||||
const CBZ_EXTENSIONS = new Set(['.cbz'])
|
||||
const CBZ_IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif'])
|
||||
@@ -23,52 +24,22 @@ function parseIssueNumber(filename: string): number | null {
|
||||
return parseInt(matches[matches.length - 1], 10)
|
||||
}
|
||||
|
||||
function getPageCount(absoluteCbzPath: string): number {
|
||||
try {
|
||||
const zip = new AdmZip(absoluteCbzPath)
|
||||
return zip
|
||||
.getEntries()
|
||||
.filter(
|
||||
(e) =>
|
||||
!e.isDirectory &&
|
||||
CBZ_IMAGE_EXTENSIONS.has(path.extname(e.entryName).toLowerCase())
|
||||
).length
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function buildIssue(
|
||||
absFilePath: string,
|
||||
filename: string,
|
||||
filePath: string,
|
||||
libraryId: string,
|
||||
isStandalone: boolean
|
||||
): ComicIssue {
|
||||
const title = path.basename(filename, path.extname(filename))
|
||||
const issueNumber = parseIssueNumber(filename)
|
||||
const pageCount = getPageCount(absFilePath)
|
||||
const coverUrl = thumbnailApiUrl(libraryId, filePath)
|
||||
|
||||
return {
|
||||
id: encodeURIComponent(filePath),
|
||||
title,
|
||||
issueNumber,
|
||||
pageCount,
|
||||
coverUrl,
|
||||
filePath,
|
||||
isStandalone,
|
||||
}
|
||||
}
|
||||
|
||||
export interface ScannedComicSeries extends ComicSeries {
|
||||
issues: ComicIssue[]
|
||||
}
|
||||
|
||||
export function scanComicsLibrary(
|
||||
interface CollectedCbz {
|
||||
absPath: string
|
||||
filename: string
|
||||
relPath: string
|
||||
isStandalone: boolean
|
||||
seriesDirName: string | null
|
||||
}
|
||||
|
||||
export async function scanComicsLibrary(
|
||||
libraryRoot: string,
|
||||
libraryId: string
|
||||
): (ComicIssue | ScannedComicSeries)[] {
|
||||
): Promise<(ComicIssue | ScannedComicSeries)[]> {
|
||||
let topEntries: fs.Dirent[]
|
||||
try {
|
||||
topEntries = fs.readdirSync(libraryRoot, { withFileTypes: true })
|
||||
@@ -76,15 +47,20 @@ export function scanComicsLibrary(
|
||||
return []
|
||||
}
|
||||
|
||||
const results: (ComicIssue | ScannedComicSeries)[] = []
|
||||
// Phase 1: Collect all CBZ paths via fast directory listing (no archive opens).
|
||||
const collected: CollectedCbz[] = []
|
||||
|
||||
for (const entry of topEntries) {
|
||||
if (HIDDEN_FILES.test(entry.name)) continue
|
||||
|
||||
if (entry.isFile() && isCbzFile(entry.name)) {
|
||||
// Standalone one-shot comic
|
||||
const absPath = path.join(libraryRoot, entry.name)
|
||||
results.push(buildIssue(absPath, entry.name, entry.name, libraryId, true))
|
||||
collected.push({
|
||||
absPath: path.join(libraryRoot, entry.name),
|
||||
filename: entry.name,
|
||||
relPath: entry.name,
|
||||
isStandalone: true,
|
||||
seriesDirName: null,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -97,32 +73,70 @@ export function scanComicsLibrary(
|
||||
continue
|
||||
}
|
||||
|
||||
const cbzFiles = subEntries.filter(
|
||||
(e) => e.isFile() && isCbzFile(e.name) && !HIDDEN_FILES.test(e.name)
|
||||
)
|
||||
const cbzFiles = subEntries
|
||||
.filter((e) => e.isFile() && isCbzFile(e.name) && !HIDDEN_FILES.test(e.name))
|
||||
.sort((a, b) => naturalCompare(a.name, b.name))
|
||||
|
||||
if (cbzFiles.length === 0) continue
|
||||
|
||||
// It's a series
|
||||
const issues: ComicIssue[] = cbzFiles
|
||||
.sort((a, b) => naturalCompare(a.name, b.name))
|
||||
.map((f) => {
|
||||
const relPath = path.join(entry.name, f.name)
|
||||
return buildIssue(path.join(dirAbsPath, f.name), f.name, relPath, libraryId, false)
|
||||
for (const f of cbzFiles) {
|
||||
collected.push({
|
||||
absPath: path.join(dirAbsPath, f.name),
|
||||
filename: f.name,
|
||||
relPath: path.join(entry.name, f.name),
|
||||
isStandalone: false,
|
||||
seriesDirName: entry.name,
|
||||
})
|
||||
|
||||
const seriesCoverUrl = issues[0]?.coverUrl ?? null
|
||||
|
||||
results.push({
|
||||
id: encodeURIComponent(entry.name),
|
||||
title: entry.name,
|
||||
coverUrl: seriesCoverUrl,
|
||||
issueCount: issues.length,
|
||||
issues,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Count pages for all CBZ files concurrently (10 at a time) by reading
|
||||
// only each archive's central directory — no full-file reads.
|
||||
const pageCounts = await mapConcurrent(collected, 10, (c) =>
|
||||
countZipImages(c.absPath, CBZ_IMAGE_EXTENSIONS)
|
||||
)
|
||||
|
||||
// Phase 3: Build the result array from collected metadata + page counts.
|
||||
const seriesMap = new Map<string, ScannedComicSeries>()
|
||||
const standaloneIssues: ComicIssue[] = []
|
||||
|
||||
for (let i = 0; i < collected.length; i++) {
|
||||
const c = collected[i]
|
||||
const coverUrl = thumbnailApiUrl(libraryId, c.relPath)
|
||||
const issue: ComicIssue = {
|
||||
id: encodeURIComponent(c.relPath),
|
||||
title: path.basename(c.filename, path.extname(c.filename)),
|
||||
issueNumber: parseIssueNumber(c.filename),
|
||||
pageCount: pageCounts[i],
|
||||
coverUrl,
|
||||
filePath: c.relPath,
|
||||
isStandalone: c.isStandalone,
|
||||
}
|
||||
|
||||
if (c.isStandalone) {
|
||||
standaloneIssues.push(issue)
|
||||
} else {
|
||||
const key = c.seriesDirName!
|
||||
if (!seriesMap.has(key)) {
|
||||
seriesMap.set(key, {
|
||||
id: encodeURIComponent(key),
|
||||
title: key,
|
||||
coverUrl, // first issue (sorted) becomes the series cover
|
||||
issueCount: 0,
|
||||
issues: [],
|
||||
})
|
||||
}
|
||||
const series = seriesMap.get(key)!
|
||||
series.issues.push(issue)
|
||||
series.issueCount++
|
||||
}
|
||||
}
|
||||
|
||||
const results: (ComicIssue | ScannedComicSeries)[] = [
|
||||
...Array.from(seriesMap.values()),
|
||||
...standaloneIssues,
|
||||
]
|
||||
return results.sort((a, b) => naturalCompare(a.title, b.title))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user