From 0612f15aad69b80bb1ca9097fb2ae12275160408 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 23 Feb 2025 10:04:20 +0000 Subject: [PATCH] Add FileNotFoundError error --- .../src/file-operations/file-operations.ts | 11 ++- .../safe-filesystem-operations.ts | 88 +++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 frontend/sync-client/src/file-operations/safe-filesystem-operations.ts diff --git a/frontend/sync-client/src/file-operations/file-operations.ts b/frontend/sync-client/src/file-operations/file-operations.ts index 8fe2a3fd..4bd29402 100644 --- a/frontend/sync-client/src/file-operations/file-operations.ts +++ b/frontend/sync-client/src/file-operations/file-operations.ts @@ -2,12 +2,17 @@ import type { Logger } from "src/tracing/logger"; import type { FileSystemOperations } from "./filesystem-operations"; import type { RelativePath } from "src/persistence/database"; import { isBinary, isFileTypeMergable, mergeText } from "sync_lib"; +import { SafeFileSystemOperations } from "./safe-filesystem-operations"; export class FileOperations { + private readonly fs: SafeFileSystemOperations; + public constructor( private readonly logger: Logger, - private readonly fs: FileSystemOperations - ) {} + fs: FileSystemOperations + ) { + this.fs = new SafeFileSystemOperations(fs); + } public async listAllFiles(): Promise { const files = await this.fs.listAllFiles(); @@ -43,7 +48,7 @@ export class FileOperations { } public async exists(path: RelativePath): Promise { - this.logger.debug(`Checking existance of ${path}`); + this.logger.debug(`Checking existence of ${path}`); return this.fs.exists(path); } diff --git a/frontend/sync-client/src/file-operations/safe-filesystem-operations.ts b/frontend/sync-client/src/file-operations/safe-filesystem-operations.ts new file mode 100644 index 00000000..3776e63a --- /dev/null +++ b/frontend/sync-client/src/file-operations/safe-filesystem-operations.ts @@ -0,0 +1,88 @@ +import { FileSystemOperations } from "dist/types"; +import type { RelativePath } from "src/persistence/database"; + +export class FileNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = "FileNotFoundError"; + } +} + +// Decorate FileSystemOperations replacing errors with FileNotFoundError +// if the accessed file doesn't exist. +export class SafeFileSystemOperations implements FileSystemOperations { + public constructor(private readonly fs: FileSystemOperations) {} + + public listAllFiles(): Promise { + return this.fs.listAllFiles(); + } + + public async read(path: RelativePath): Promise { + return this.safeOperation(path, () => this.fs.read(path)); + } + + public async write(path: RelativePath, content: Uint8Array): Promise { + return this.fs.write(path, content); + } + + public async atomicUpdateText( + path: RelativePath, + updater: (currentContent: string) => string + ): Promise { + return this.safeOperation(path, () => + this.fs.atomicUpdateText(path, updater) + ); + } + + public async getFileSize(path: RelativePath): Promise { + return this.safeOperation(path, () => this.fs.getFileSize(path)); + } + + public async getModificationTime(path: RelativePath): Promise { + return this.safeOperation(path, () => + this.fs.getModificationTime(path) + ); + } + + public async exists(path: RelativePath): Promise { + return this.fs.exists(path); + } + + public async createDirectory(path: RelativePath): Promise { + return this.fs.createDirectory(path); + } + + public async delete(path: RelativePath): Promise { + return this.fs.delete(path); + } + + public async rename( + oldPath: RelativePath, + newPath: RelativePath + ): Promise { + return this.safeOperation(oldPath, () => + this.fs.rename(oldPath, newPath) + ); + } + + private async safeOperation( + path: RelativePath, + operation: () => Promise + ): Promise { + // Without locking the file, this isn't atomic, however, it's good enough practicaly. + // This will only break if the file exists, gets deleted and then immediately + // recreated while `operation` is running. + if (!(await this.fs.exists(path))) { + throw new FileNotFoundError(path); + } + try { + return await operation(); + } catch (error) { + if (await this.fs.exists(path)) { + throw error; + } else { + throw new FileNotFoundError(path); + } + } + } +}