....
This commit is contained in:
parent
3dab48f58b
commit
c869f0a880
1 changed files with 461 additions and 578 deletions
|
|
@ -1,6 +1,10 @@
|
|||
import { v4 as uuid } from "uuid";
|
||||
import type { FileOperations } from "../file-operations/file-operations";
|
||||
import type { DocumentId, EngineState, RelativePath } from "../persistence/database";
|
||||
import type {
|
||||
Database,
|
||||
DocumentId,
|
||||
RelativePath
|
||||
} from "../persistence/database";
|
||||
import type { VaultSnapshot } from "../services/types/VaultSnapshot";
|
||||
import { EMPTY_HASH, hash } from "../utils/hash";
|
||||
import {
|
||||
|
|
@ -16,28 +20,27 @@ import {
|
|||
export type { LocalChange } from "./local-changes";
|
||||
|
||||
interface ScanOptions {
|
||||
next: EngineState;
|
||||
database: Database;
|
||||
guard: () => void;
|
||||
changes: LocalChange[];
|
||||
files: FileOperations;
|
||||
ignored: (path: RelativePath) => boolean;
|
||||
oversized: (size: number) => boolean;
|
||||
commit: (next: EngineState) => Promise<void>;
|
||||
}
|
||||
|
||||
/** Scans are complete or fail. Missing unmaterialized/ignored entries are not deletions. */
|
||||
export async function scanLocalFiles(
|
||||
{
|
||||
next,
|
||||
database,
|
||||
guard,
|
||||
changes: queuedChanges,
|
||||
files,
|
||||
ignored,
|
||||
oversized,
|
||||
commit
|
||||
oversized
|
||||
}: ScanOptions,
|
||||
initial?: VaultSnapshot
|
||||
): Promise<Set<RelativePath>> {
|
||||
const next = structuredClone(database.state);
|
||||
// Leave the prefix queued until its resulting state is saved.
|
||||
// A persisted change ID makes replay safe after an uncertain save.
|
||||
const changes = [...queuedChanges];
|
||||
|
|
@ -61,7 +64,7 @@ export async function scanLocalFiles(
|
|||
);
|
||||
}
|
||||
const present = new Set(paths);
|
||||
const snapshots = new Map<RelativePath, { hash: string }>();
|
||||
const hashes = new Map<RelativePath, string>();
|
||||
for (const path of paths) {
|
||||
if (ignored(path)) {
|
||||
unsyncablePaths.add(path);
|
||||
|
|
@ -77,7 +80,7 @@ export async function scanLocalFiles(
|
|||
const snapshot = await files.fs.read(path);
|
||||
if (!snapshot) throw new Error(`File changed during scan: ${path}`);
|
||||
if (!oversized(snapshot.content.length)) {
|
||||
snapshots.set(path, { hash: await hash(snapshot.content) });
|
||||
hashes.set(path, await hash(snapshot.content));
|
||||
} else {
|
||||
unsyncablePaths.add(path);
|
||||
}
|
||||
|
|
@ -91,20 +94,18 @@ export async function scanLocalFiles(
|
|||
)
|
||||
);
|
||||
const knownPaths = new Set(Object.values(next.local));
|
||||
const unknown = [...snapshots.keys()].filter(
|
||||
(path) => !knownPaths.has(path)
|
||||
);
|
||||
const unknown = [...hashes.keys()].filter((path) => !knownPaths.has(path));
|
||||
for (const path of unknown) {
|
||||
const snapshot = snapshots.get(path)!;
|
||||
const digest = hashes.get(path)!;
|
||||
const matches = [...missing].filter(
|
||||
(id) => next.documents[id]?.observedHash === snapshot.hash
|
||||
(id) => next.documents[id]?.observedHash === digest
|
||||
);
|
||||
const sameHash = unknown.filter(
|
||||
(other) => snapshots.get(other)?.hash === snapshot.hash
|
||||
(other) => hashes.get(other) === digest
|
||||
);
|
||||
let id: DocumentId | undefined;
|
||||
if (
|
||||
snapshot.hash !== EMPTY_HASH &&
|
||||
digest !== EMPTY_HASH &&
|
||||
matches.length === 1 &&
|
||||
sameHash.length === 1
|
||||
) {
|
||||
|
|
@ -131,24 +132,21 @@ export async function scanLocalFiles(
|
|||
if (next.excluded[id] === undefined)
|
||||
next.excluded[id] = next.fileManifest.entries[id] ?? null;
|
||||
}
|
||||
const snapshot = snapshots.get(path);
|
||||
if (snapshot) {
|
||||
const digest = hashes.get(path);
|
||||
if (digest !== undefined) {
|
||||
const doc = (next.documents[id] ??= {});
|
||||
// The old history no longer contains this base. Preserve any edit
|
||||
// made during recovery as a new file instead of overwriting it.
|
||||
if (
|
||||
doc.base?.vaultUpdateId === null &&
|
||||
doc.base.hash !== snapshot.hash
|
||||
) {
|
||||
if (doc.base?.vaultUpdateId === null && doc.base.hash !== digest) {
|
||||
const recoveredId = uuid();
|
||||
next.local[recoveredId] = path;
|
||||
next.documents[recoveredId] = { observedHash: snapshot.hash };
|
||||
next.documents[recoveredId] = { observedHash: digest };
|
||||
delete next.local[id];
|
||||
} else doc.observedHash = snapshot.hash;
|
||||
} else doc.observedHash = digest;
|
||||
}
|
||||
}
|
||||
guard();
|
||||
await commit(next);
|
||||
await database.commit(next);
|
||||
queuedChanges.splice(0, changes.length);
|
||||
guard();
|
||||
return unsyncablePaths;
|
||||
|
|
|
|||
Loading…
Reference in a new issue