power loss
This commit is contained in:
parent
0f6b618c0b
commit
d56084e4bc
2 changed files with 18667 additions and 3902 deletions
|
|
@ -51,7 +51,7 @@ test("NodeFileSystemOperations - exists with forward slashes", async () => {
|
|||
}
|
||||
});
|
||||
|
||||
test("NodeFileSystemOperations - delete with forward slashes", async () => {
|
||||
test("NodeFileSystemOperations - deleteFile with forward slashes", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "vaultlink-test-"));
|
||||
const fsOps = new NodeFileSystemOperations(tempDir);
|
||||
|
||||
|
|
@ -59,8 +59,10 @@ test("NodeFileSystemOperations - delete with forward slashes", async () => {
|
|||
await fsOps.write("test.txt", new TextEncoder().encode("test"));
|
||||
assert.equal(await fsOps.exists("test.txt"), true);
|
||||
|
||||
await fsOps.delete("test.txt");
|
||||
await fsOps.deleteFile("test.txt");
|
||||
assert.equal(await fsOps.exists("test.txt"), false);
|
||||
|
||||
await fsOps.deleteFile("test.txt");
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
|
|
@ -154,9 +156,46 @@ test("NodeFileSystemOperations - handles paths with forward slashes on all platf
|
|||
const readContent = await fsOps.read(testPath);
|
||||
assert.equal(new TextDecoder().decode(readContent), "test");
|
||||
|
||||
await fsOps.delete(testPath);
|
||||
await fsOps.deleteFile(testPath);
|
||||
assert.equal(await fsOps.exists(testPath), false);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("NodeFileSystemOperations - delete prunes empty directory trees", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "vaultlink-test-"));
|
||||
const fsOps = new NodeFileSystemOperations(tempDir);
|
||||
|
||||
try {
|
||||
await fs.mkdir(path.join(tempDir, "parent", "child"), {
|
||||
recursive: true
|
||||
});
|
||||
|
||||
await fsOps.delete("parent");
|
||||
assert.equal(await fsOps.exists("parent"), false);
|
||||
|
||||
await fsOps.delete("parent");
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("NodeFileSystemOperations - delete does not remove files", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "vaultlink-test-"));
|
||||
const fsOps = new NodeFileSystemOperations(tempDir);
|
||||
|
||||
try {
|
||||
await fsOps.write(
|
||||
"parent/file.txt",
|
||||
new TextEncoder().encode("preserve me")
|
||||
);
|
||||
|
||||
await assert.rejects(async () => fsOps.delete("parent"));
|
||||
assert.equal(await fsOps.exists("parent/file.txt"), true);
|
||||
await assert.rejects(async () => fsOps.delete("parent/file.txt"));
|
||||
assert.equal(await fsOps.exists("parent/file.txt"), true);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { toUnixPath } from "./path-utils";
|
|||
export const VAULTLINK_DIR = ".vaultlink";
|
||||
|
||||
export class NodeFileSystemOperations implements FileSystemOperations {
|
||||
public constructor(private readonly basePath: string) { }
|
||||
public constructor(private readonly basePath: string) {}
|
||||
|
||||
public async listFilesRecursively(
|
||||
directory: RelativePath | undefined
|
||||
|
|
@ -105,9 +105,36 @@ export class NodeFileSystemOperations implements FileSystemOperations {
|
|||
}
|
||||
|
||||
public async delete(relativePath: RelativePath): Promise<void> {
|
||||
if (!relativePath) {
|
||||
throw new Error("Cannot delete the vault root");
|
||||
}
|
||||
|
||||
const fullPath = path.join(this.basePath, relativePath);
|
||||
try {
|
||||
await fs.unlink(fullPath);
|
||||
await this.deleteDirectoryTree(fullPath);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to delete directory ${fullPath}: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteFile(relativePath: RelativePath): Promise<void> {
|
||||
const fullPath = path.join(this.basePath, relativePath);
|
||||
try {
|
||||
const entry = await fs.lstat(fullPath).catch((error: unknown) => {
|
||||
if (this.isMissing(error)) return undefined;
|
||||
throw error;
|
||||
});
|
||||
if (!entry) return;
|
||||
if (!entry.isFile() || entry.nlink !== 1) {
|
||||
throw new Error("Cannot unlink a non-regular or linked file");
|
||||
}
|
||||
|
||||
await fs.unlink(fullPath).catch((error: unknown) => {
|
||||
if (!this.isMissing(error)) throw error;
|
||||
});
|
||||
await this.syncDirectory(path.dirname(fullPath));
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to delete file ${fullPath}: ${error instanceof Error ? error.message : String(error)}`
|
||||
|
|
@ -171,6 +198,40 @@ export class NodeFileSystemOperations implements FileSystemOperations {
|
|||
}
|
||||
}
|
||||
|
||||
private async deleteDirectoryTree(fullPath: string): Promise<void> {
|
||||
const entry = await fs.lstat(fullPath).catch((error: unknown) => {
|
||||
if (this.isMissing(error)) return undefined;
|
||||
throw error;
|
||||
});
|
||||
if (!entry) return;
|
||||
if (!entry.isDirectory()) {
|
||||
throw new Error("Cannot delete a regular file as a directory");
|
||||
}
|
||||
|
||||
for (const child of await fs.readdir(fullPath, {
|
||||
withFileTypes: true
|
||||
})) {
|
||||
if (!child.isDirectory()) {
|
||||
throw new Error(
|
||||
`Directory contains a non-directory entry: ${path.join(fullPath, child.name)}`
|
||||
);
|
||||
}
|
||||
await this.deleteDirectoryTree(path.join(fullPath, child.name));
|
||||
}
|
||||
|
||||
await fs.rmdir(fullPath);
|
||||
await this.syncDirectory(path.dirname(fullPath));
|
||||
}
|
||||
|
||||
private isMissing(error: unknown): boolean {
|
||||
return (
|
||||
typeof error === "object" &&
|
||||
error !== null &&
|
||||
"code" in error &&
|
||||
error.code === "ENOENT"
|
||||
);
|
||||
}
|
||||
|
||||
private async walkDirectory(
|
||||
relativePath: string,
|
||||
files: RelativePath[]
|
||||
|
|
|
|||
Loading…
Reference in a new issue