perf(startup): inspect storage sizes without loading healthy files

This commit is contained in:
s-qin committed 2026-10-23 23:54:13 +08:00
1 parent ce0524d49f
commit fc2b35e0dc
2 files changed
+60 -9

No files matched your search

@@ -31,7 +31,7 @@ export interface IncompleteDocumentsDependencies {
readonly localDatabase: Pick<LiveSyncLocalDB, "findAllNormalDocs" | "getDBEntryFromMeta">; readonly localDatabase: Pick<LiveSyncLocalDB, "findAllNormalDocs" | "getDBEntryFromMeta">;
readonly getPath: StartupPathReader; readonly getPath: StartupPathReader;
readonly isTargetFile: (path: string) => Promise<boolean>; readonly isTargetFile: (path: string) => Promise<boolean>;
readonly storageAccess: Pick<StorageAccess, "readHiddenFileBinary" | "getFileStub">; readonly storageAccess: Pick<StorageAccess, "readHiddenFileBinary" | "statHidden" | "getFileStub">;
readonly fileHandler: Pick<IFileHandler, "storeFileToDB">; readonly fileHandler: Pick<IFileHandler, "storeFileToDB">;
readonly keyValueDB: Pick<KeyValueDatabase, "get" | "set">; readonly keyValueDB: Pick<KeyValueDatabase, "get" | "set">;
readonly noticeGroups: NoticeGroups; readonly noticeGroups: NoticeGroups;
@@ -94,16 +94,11 @@ export async function checkIncompleteDocuments(
} }
const isConflicted = metaDoc?._conflicts && metaDoc._conflicts.length > 0; const isConflicted = metaDoc?._conflicts && metaDoc._conflicts.length > 0;
let storageFileContent; const storageStat = await dependencies.storageAccess.statHidden(path);
try { if (!storageStat || storageStat.type !== "file") {
storageFileContent = await dependencies.storageAccess.readHiddenFileBinary(path);
} catch (e) {
Logger(`Failed to read file ${path}: Possibly unprocessed or missing`);
Logger(e, LOG_LEVEL_VERBOSE);
continue; continue;
} }
// const storageFileBlob = createBlob(storageFileContent); let sizeOnStorage = storageStat.size;
const sizeOnStorage = storageFileContent.byteLength;
const recordedSize = doc.size; const recordedSize = doc.size;
const docBlob = readAsBlob(doc); const docBlob = readAsBlob(doc);
const actualSize = docBlob.size; const actualSize = docBlob.size;
@@ -113,6 +108,15 @@ export async function checkIncompleteDocuments(
sizeOnStorage !== recordedSize || sizeOnStorage !== recordedSize ||
isConflicted isConflicted
) { ) {
let storageFileContent;
try {
storageFileContent = await dependencies.storageAccess.readHiddenFileBinary(path);
} catch (e) {
Logger(`Failed to read file ${path}: Possibly unprocessed or missing`);
Logger(e, LOG_LEVEL_VERBOSE);
continue;
}
sizeOnStorage = storageFileContent.byteLength;
const contentMatched = await isDocContentSame(doc.data, storageFileContent); const contentMatched = await isDocContentSame(doc.data, storageFileContent);
errorFiles.push({ errorFiles.push({
path, path,
@@ -125,6 +129,17 @@ export async function checkIncompleteDocuments(
Logger( Logger(
`Size mismatch for ${path}: ${recordedSize} (DB Recorded) , ${actualSize} (DB Stored) , ${sizeOnStorage} (Storage Stored), ${contentMatched ? "Content Matched" : "Content Mismatched"} ${isConflicted ? "Conflicted" : "Not Conflicted"}` `Size mismatch for ${path}: ${recordedSize} (DB Recorded) , ${actualSize} (DB Stored) , ${sizeOnStorage} (Storage Stored), ${contentMatched ? "Content Matched" : "Content Mismatched"} ${isConflicted ? "Conflicted" : "Not Conflicted"}`
); );
} else {
const currentStat = await dependencies.storageAccess.statHidden(path);
if (
!currentStat ||
currentStat.type !== "file" ||
currentStat.size !== storageStat.size ||
currentStat.mtime !== storageStat.mtime ||
currentStat.ctime !== storageStat.ctime
) {
throw new Error("Storage changed during the incomplete document check");
}
} }
} }
if (errorFiles.length == 0) { if (errorFiles.length == 0) {
@@ -99,6 +99,7 @@ function createDependencies(findAllNormalDocs: FindAllNormalDocs = noDocuments,
isTargetFile: vi.fn(async () => true), isTargetFile: vi.fn(async () => true),
storageAccess: { storageAccess: {
readHiddenFileBinary, readHiddenFileBinary,
statHidden: vi.fn(async (path: string) => getFixture(path)?.stub.stat ?? null),
getFileStub, getFileStub,
}, },
fileHandler: { storeFileToDB }, fileHandler: { storeFileToDB },
@@ -157,6 +158,41 @@ describe("checkIncompleteDocuments", () => {
expect(noticeGroups.finish).toHaveBeenCalledWith("startup-integrity-check"); expect(noticeGroups.finish).toHaveBeenCalledWith("startup-integrity-check");
}); });
it("does not read healthy storage contents only to determine their size", async () => {
const healthy = documentFixture("healthy.md", { storageContent: "abc" });
const fixture = createDependencies(documentsFrom([healthy]), [healthy]);
await expect(checkIncompleteDocuments(fixture.dependencies)).resolves.toBe(true);
expect(fixture.readHiddenFileBinary).not.toHaveBeenCalled();
expect(fixture.askSelectStringDialogue).not.toHaveBeenCalled();
expect(fixture.dependencies.keyValueDB.set).toHaveBeenCalledWith("checkIncompleteDocs", true);
});
it("does not mark a scan complete when storage changes during the size check", async () => {
const healthy = documentFixture("changing.md", { storageContent: "abc" });
const fixture = createDependencies(documentsFrom([healthy]), [healthy]);
vi.mocked(fixture.dependencies.storageAccess.statHidden)
.mockResolvedValueOnce(healthy.stub.stat)
.mockResolvedValueOnce({ ...healthy.stub.stat, mtime: 3 });
await expect(checkIncompleteDocuments(fixture.dependencies)).rejects.toThrow("Storage changed");
expect(fixture.readHiddenFileBinary).not.toHaveBeenCalled();
expect(fixture.dependencies.keyValueDB.set).not.toHaveBeenCalled();
});
it("still reads conflicted storage for the existing repair dialogue", async () => {
const conflicted = documentFixture("conflicted.md", { storageContent: "abc", conflicts: ["2-other"] });
const fixture = createDependencies(documentsFrom([conflicted]), [conflicted]);
fixture.askSelectStringDialogue.mockImplementation(selectButton(0));
await expect(checkIncompleteDocuments(fixture.dependencies)).resolves.toBe(true);
expect(fixture.readHiddenFileBinary).toHaveBeenCalledWith("conflicted.md");
expect(fixture.dependencies.keyValueDB.set).not.toHaveBeenCalled();
});
it("repairs a recoverable document when FIX is selected", async () => { it("repairs a recoverable document when FIX is selected", async () => {
const recoverable = documentFixture("recoverable.md"); const recoverable = documentFixture("recoverable.md");
const fixture = createDependencies(documentsFrom([recoverable]), [recoverable]); const fixture = createDependencies(documentsFrom([recoverable]), [recoverable]);