Cache library index and refresh changed manga
This commit is contained in:
1 parent
eef4ffa07c
commit
8ac27e1f3d
6 files changed
+269
-30
No files matched your search
+69
-1
@@ -164,4 +164,72 @@ async function scanLibrary(root, oldCache = {}) {
|
||||
return { index: { mangas, byId: new Map(mangas.map(m => [m.id, m])) }, cache };
|
||||
}
|
||||
|
||||
module.exports = { scanLibrary, parseKey, sizeFromBuffer, imageSize };
|
||||
// Book-directory renames are the publisher's atomic commit point. A shallow
|
||||
// stamp pass costs one stat per book, rather than one stat/JSON read per chapter.
|
||||
// In-place chapter.json edits are picked up by the scheduled deep reconciliation.
|
||||
async function fileStamp(file) {
|
||||
try { const s = await fsp.stat(file); return `${s.mtimeMs}:${s.size}`; }
|
||||
catch (e) { if (e.code === 'ENOENT') return ''; throw e; }
|
||||
}
|
||||
async function libraryStamps(root) {
|
||||
const stamps = {};
|
||||
for (const name of (await list(root)).dirs) {
|
||||
const mangaDir = path.join(root, name), books = {};
|
||||
const listed = await list(mangaDir);
|
||||
for (const bookName of listed.dirs.filter(parseKey)) {
|
||||
const bookDir = path.join(mangaDir, bookName);
|
||||
books[bookName] = [await fileStamp(bookDir), await fileStamp(path.join(bookDir, 'book.json'))];
|
||||
}
|
||||
stamps[name] = [await fileStamp(mangaDir), await fileStamp(path.join(mangaDir, 'manga.json')), books];
|
||||
}
|
||||
return stamps;
|
||||
}
|
||||
|
||||
async function scanChangedLibrary(root, prior, oldCache, oldStamps, stamps) {
|
||||
stamps = stamps || await libraryStamps(root);
|
||||
const names = new Set([...Object.keys(oldStamps || {}), ...Object.keys(stamps)]);
|
||||
const changed = [...names].filter(name => JSON.stringify(oldStamps?.[name]) !== JSON.stringify(stamps[name]));
|
||||
if (!changed.length) return { index: prior, cache: oldCache, stamps, changed };
|
||||
const byId = new Map(prior.mangas.map(m => [m.id, m]));
|
||||
const cache = { ...oldCache };
|
||||
for (const name of changed) {
|
||||
byId.delete(name);
|
||||
for (const key of Object.keys(cache)) if (key.startsWith(name + '/')) delete cache[key];
|
||||
if (!Object.hasOwn(stamps, name)) continue;
|
||||
const m = await scanManga(root, name, oldCache, cache);
|
||||
if (m.chapters.length) byId.set(name, m);
|
||||
}
|
||||
const mangas = [...byId.values()].sort((a, b) => cmp(a.title, b.title));
|
||||
return { index: { mangas, byId: new Map(mangas.map(m => [m.id, m])) }, cache, stamps, changed };
|
||||
}
|
||||
|
||||
// Store each chapter only once. Maps and book chapter references are rebuilt
|
||||
// from the snapshot, which is a disposable cache rather than library authority.
|
||||
function snapshotIndex(index) {
|
||||
return index.mangas.map(m => ({ ...m, chapterById: undefined,
|
||||
books: m.books.map(b => ({ ...b, chapters: b.chapters.map(ch => ch.id) })) }));
|
||||
}
|
||||
function restoreIndex(mangas) {
|
||||
if (!Array.isArray(mangas)) throw new Error('Invalid library index snapshot');
|
||||
const byId = new Map();
|
||||
for (const m of mangas) {
|
||||
if (!m || typeof m.id !== 'string' || !Array.isArray(m.books) || !Array.isArray(m.chapters) || byId.has(m.id))
|
||||
throw new Error('Invalid manga in library index snapshot');
|
||||
m.chapterById = new Map();
|
||||
for (const ch of m.chapters) {
|
||||
if (!ch || typeof ch.id !== 'string' || !Array.isArray(ch.versions) || ch.versions.some(v => !Array.isArray(v.pages)))
|
||||
throw new Error('Invalid chapter in library index snapshot');
|
||||
m.chapterById.set(ch.id, ch);
|
||||
}
|
||||
for (const book of m.books) {
|
||||
if (!Array.isArray(book.chapters) || book.chapters.some(id => !m.chapterById.has(id)))
|
||||
throw new Error('Invalid book in library index snapshot');
|
||||
book.chapters = book.chapters.map(id => m.chapterById.get(id));
|
||||
}
|
||||
byId.set(m.id, m);
|
||||
}
|
||||
return { mangas, byId };
|
||||
}
|
||||
|
||||
module.exports = { scanLibrary, scanChangedLibrary, libraryStamps, snapshotIndex, restoreIndex,
|
||||
parseKey, sizeFromBuffer, imageSize };
|
||||
Reference in new issue
Block a user