mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
95a7b94184
Store file inodes as numeric rather than bigint on PostgreSQL, and read/process them as BigInt in Node.js. This preserves exact 64-bit inodes from filesystems like MergerFS (hybrid-hash) and avoids folder mis-merging or disabled rename detection. Closes #618
61 lines
2.1 KiB
TypeScript
61 lines
2.1 KiB
TypeScript
import { mkdir, stat, writeFile } from 'fs/promises';
|
|
import { join } from 'path';
|
|
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
import { bookFiles, books } from '../src/db/schema';
|
|
import {
|
|
closeScannerE2EContext,
|
|
createScannerE2EContext,
|
|
loadLibraryBookState,
|
|
seedLibrary,
|
|
triggerAndWaitForLibraryScan,
|
|
type ScannerE2EContext,
|
|
} from './e2e/scanner/scanner-harness';
|
|
|
|
const mergerFsRoot = process.env.MERGERFS_TEST_ROOT;
|
|
|
|
describe.skipIf(!mergerFsRoot)('MergerFS inode scanning', () => {
|
|
let ctx: ScannerE2EContext;
|
|
let libraryId: number;
|
|
|
|
beforeAll(async () => {
|
|
await mkdir(join(mergerFsRoot!, 'ExistingBook'), { recursive: true });
|
|
await writeFile(join(mergerFsRoot!, 'ExistingBook', 'book.epub'), 'shared-content');
|
|
ctx = await createScannerE2EContext();
|
|
({ libraryId } = await seedLibrary(ctx.db, {
|
|
rootPath: mergerFsRoot!,
|
|
mode: 'book_per_folder',
|
|
name: 'mergerfs-inode-e2e',
|
|
}));
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (ctx) await closeScannerE2EContext(ctx);
|
|
});
|
|
|
|
it('persists exact oversized inodes and keeps identical files in distinct folders', async () => {
|
|
const existingPath = join(mergerFsRoot!, 'ExistingBook', 'book.epub');
|
|
const existingStat = await stat(existingPath, { bigint: true });
|
|
const maxSafeIno = BigInt(Number.MAX_SAFE_INTEGER);
|
|
expect(existingStat.ino < -maxSafeIno || existingStat.ino > maxSafeIno).toBe(true);
|
|
|
|
await triggerAndWaitForLibraryScan(ctx, libraryId);
|
|
|
|
const [storedExisting] = await ctx.db
|
|
.select({ ino: bookFiles.ino })
|
|
.from(bookFiles)
|
|
.innerJoin(books, eq(books.id, bookFiles.bookId))
|
|
.where(eq(books.libraryId, libraryId));
|
|
expect(storedExisting.ino).toBe(existingStat.ino);
|
|
|
|
await mkdir(join(mergerFsRoot!, 'NewBook'), { recursive: true });
|
|
await writeFile(join(mergerFsRoot!, 'NewBook', 'book.epub'), 'shared-content');
|
|
await triggerAndWaitForLibraryScan(ctx, libraryId);
|
|
|
|
const state = await loadLibraryBookState(ctx.db, libraryId);
|
|
expect(state).toHaveLength(2);
|
|
expect(state.map((book) => book.folderPath)).toEqual([join(mergerFsRoot!, 'ExistingBook'), join(mergerFsRoot!, 'NewBook')]);
|
|
});
|
|
});
|