This commit is contained in:
Andras Schmelczer 2026-09-26 20:22:16 +01:00
commit b07aa589ff
3 changed files with 728 additions and 530 deletions

View file

@ -1,20 +1,15 @@
import { reconcile } from "reconcile-text";
import type {
FileSnapshot,
FileSystemOperations
} from "./filesystem-operations";
import type {
Database,
EngineState,
StoredSnapshot
} from "../persistence/database";
import { isInternalPath } from "../utils/portable-path";
import { FilePaths } from "./file-paths";
import type { FileSystemOperations } from "./filesystem-operations";
import type { Database, EngineState } from "../persistence/database";
import {
fromStoredSnapshot,
toStoredSnapshot,
mergeContent
} from "../sync-operations/content";
type FileSnapshot,
type StoredSnapshot
} from "../snapshot";
import { isInternalPath } from "../utils/portable-path";
import { FilePaths } from "./file-paths";
import { mergeContent } from "../sync-operations/content";
import { hash } from "../utils/hash";
import { isBinary } from "../utils/is-binary";
import { isFileTypeMergeable } from "../utils/is-file-type-mergeable";
@ -89,7 +84,7 @@ export class FileOperations {
): Promise<void> {
const state = structuredClone(this.applying ?? this.database.state);
if (!state.initialized) return;
if (state.lastSeenUpdateId === undefined) return;
for (const pending of unappliedChanges(state, previous))
applyLocalChange(state, pending, () => false);
@ -118,7 +113,7 @@ export class FileOperations {
};
guard();
const current = structuredClone(this.database.state);
current.initialized = next.initialized;
current.lastSeenUpdateId = next.lastSeenUpdateId;
this.applying = current;
const paths = new Set<string>();
const observeChanges = async (): Promise<void> => {
@ -145,7 +140,7 @@ export class FileOperations {
...Object.keys(next.local)
])) {
await observeChanges();
let from = current.documents[id]?.materialized
let from = current.documents[id]?.observedHash !== undefined
? current.local[id]
: undefined;
let to = next.local[id];
@ -166,7 +161,6 @@ export class FileOperations {
);
(next.excluded ??= {})[id] =
current.fileManifest.entries[id] ?? null;
(next.protectedPaths ??= []).push(from);
continue;
}
}
@ -187,7 +181,7 @@ export class FileOperations {
mutate
);
next.local[id] = to;
from = current.documents[id]?.materialized
from = current.documents[id]?.observedHash !== undefined
? current.local[id]
: undefined;
if (from !== undefined) {
@ -211,7 +205,7 @@ export class FileOperations {
.mergeableFileExtensions;
await mutate(async () => {
current.local[id] = to;
current.documents[id] = { materialized: true };
current.documents[id] = { observedHash: "" };
next.documents[id].observedHash =
await this.updateContent(
to,
@ -222,7 +216,6 @@ export class FileOperations {
});
}
paths.add(to);
next.documents[id].materialized = true;
current.local[id] = to;
current.documents[id] = structuredClone(next.documents[id]);
}

View file

@ -29,13 +29,12 @@ export class FilePaths {
for (const child of children) {
const occupant = Object.keys(current.local).find(
(key) =>
current.documents[key]?.materialized &&
current.documents[key]?.observedHash !== undefined &&
current.local[key] === child
);
const info = await this.fs.stat(child);
if (
!info ||
next.protectedPaths?.includes(child) ||
this.isProtectedFile(child, info.size) ||
(obstruction.kind === "directory" &&
(occupant === undefined ||
@ -59,8 +58,8 @@ export class FilePaths {
await this.fs.rename(occupant.path, displaced);
});
if (!current.documents[occupant.id]) {
current.documents[occupant.id] = { materialized: true };
next.documents[occupant.id] = { materialized: true };
current.documents[occupant.id] = { observedHash: "" };
next.documents[occupant.id] = { observedHash: "" };
next.local[occupant.id] = displaced;
} else if (next.local[occupant.id] === occupant.path)
next.local[occupant.id] = displaced;

View file

@ -1,11 +1,6 @@
import type { CursorPosition, TextWithCursors } from "reconcile-text";
import type { TextWithCursors } from "reconcile-text";
import type { RelativePath } from "../persistence/database";
export interface FileSnapshot {
content: Uint8Array;
/** Optional editor selections, moved with the file and applied with its bytes. */
cursors?: CursorPosition[];
}
import type { FileSnapshot } from "../snapshot";
/** Filesystem operations act on the current user-owned namespace.
* Paths are vault-relative. Never follow symlinks (including ancestor symlinks).