diff --git a/frontend/sync-client/src/file-operations/safe-filesystem-operations.ts b/frontend/sync-client/src/file-operations/safe-filesystem-operations.ts index e7c1d29..94ee8ad 100644 --- a/frontend/sync-client/src/file-operations/safe-filesystem-operations.ts +++ b/frontend/sync-client/src/file-operations/safe-filesystem-operations.ts @@ -2,7 +2,7 @@ import type { FileSystemOperations } from "dist/types"; import type { RelativePath } from "src/persistence/database"; export class FileNotFoundError extends Error { - constructor(message: string) { + public constructor(message: string) { super(message); this.name = "FileNotFoundError"; } diff --git a/frontend/sync-client/src/sync-operations/syncer.ts b/frontend/sync-client/src/sync-operations/syncer.ts index 0c764c3..652d9c5 100644 --- a/frontend/sync-client/src/sync-operations/syncer.ts +++ b/frontend/sync-client/src/sync-operations/syncer.ts @@ -95,16 +95,6 @@ export class Syncer { ); } - private async syncRemotelyUpdatedFile( - remoteVersion: components["schemas"]["DocumentVersionWithoutContent"] - ): Promise { - await this.syncQueue.add(async () => - this.internalSyncer.unrestrictedSyncRemotelyUpdatedFile( - remoteVersion - ) - ); - } - public async scheduleSyncForOfflineChanges(): Promise { if (!this.settings.getSettings().isSyncEnabled) { this.logger.debug( @@ -133,6 +123,52 @@ export class Syncer { } } + public async applyRemoteChangesLocally(): Promise { + if (!this.settings.getSettings().isSyncEnabled) { + this.logger.debug( + `Syncing is disabled, not fetching remote changes` + ); + return; + } + + if (this.runningApplyRemoteChangesLocally != null) { + this.logger.debug( + "Applying remote changes locally is already in progress" + ); + return this.runningApplyRemoteChangesLocally; + } + + try { + this.runningApplyRemoteChangesLocally = + this.internalApplyRemoteChangesLocally(); + await this.runningApplyRemoteChangesLocally; + this.logger.info("All remote changes have been applied locally"); + } catch (e) { + this.logger.error(`Failed to apply remote changes locally: ${e}`); + throw e; + } finally { + this.runningApplyRemoteChangesLocally = undefined; + } + } + + public async reset(): Promise { + this.syncQueue.clear(); + await this.syncQueue.onEmpty(); + this.remainingOperationsListeners.forEach((listener) => { + listener(0); + }); + } + + private async syncRemotelyUpdatedFile( + remoteVersion: components["schemas"]["DocumentVersionWithoutContent"] + ): Promise { + await this.syncQueue.add(async () => + this.internalSyncer.unrestrictedSyncRemotelyUpdatedFile( + remoteVersion + ) + ); + } + private async internalScheduleSyncForOfflineChanges(): Promise { const allLocalFiles = await this.operations.listAllFiles(); @@ -226,34 +262,6 @@ export class Syncer { ); } - public async applyRemoteChangesLocally(): Promise { - if (!this.settings.getSettings().isSyncEnabled) { - this.logger.debug( - `Syncing is disabled, not fetching remote changes` - ); - return; - } - - if (this.runningApplyRemoteChangesLocally != null) { - this.logger.debug( - "Applying remote changes locally is already in progress" - ); - return this.runningApplyRemoteChangesLocally; - } - - try { - this.runningApplyRemoteChangesLocally = - this.internalApplyRemoteChangesLocally(); - await this.runningApplyRemoteChangesLocally; - this.logger.info("All remote changes have been applied locally"); - } catch (e) { - this.logger.error(`Failed to apply remote changes locally: ${e}`); - throw e; - } finally { - this.runningApplyRemoteChangesLocally = undefined; - } - } - private async internalApplyRemoteChangesLocally(): Promise { const remote = await this.syncService.getAll( this.database.getLastSeenUpdateId() @@ -281,14 +289,6 @@ export class Syncer { } } - public async reset(): Promise { - this.syncQueue.clear(); - await this.syncQueue.onEmpty(); - this.remainingOperationsListeners.forEach((listener) => { - listener(0); - }); - } - private emitRemainingOperationsChange(remainingOperations: number): void { this.remainingOperationsListeners.forEach((listener) => { listener(remainingOperations); diff --git a/frontend/test-client/src/agent/mock-client.ts b/frontend/test-client/src/agent/mock-client.ts index 6fc8f15..6dc5407 100644 --- a/frontend/test-client/src/agent/mock-client.ts +++ b/frontend/test-client/src/agent/mock-client.ts @@ -7,9 +7,9 @@ import { SyncClient } from "sync-client"; import { assert } from "../utils/assert"; export class MockClient implements FileSystemOperations { - protected readonly localFiles: Record = {}; + protected readonly localFiles = new Map(); protected client!: SyncClient; - protected data: unknown = ""; + protected data: object | undefined = undefined; public constructor( private readonly initialSettings: Partial @@ -18,15 +18,17 @@ export class MockClient implements FileSystemOperations { public async init(): Promise { this.client = await SyncClient.create(this, { load: async () => this.data, - save: async (data: unknown) => void (this.data = data) + save: async (data) => void (this.data = data) }); await Promise.all( Object.keys(this.initialSettings).map(async (key) => { - return this.client.settings.setSetting( - key as keyof SyncSettings, - this.initialSettings[key as keyof SyncSettings] - ); + if (key in this.client.settings) { + return this.client.settings.setSetting( + key as keyof SyncSettings, // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion + this.initialSettings[key as keyof SyncSettings] // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion + ); + } }) ); @@ -37,46 +39,44 @@ export class MockClient implements FileSystemOperations { } public async listAllFiles(): Promise { - return Object.keys(this.localFiles); + return Array.from(this.localFiles.keys()); } public async read(path: RelativePath): Promise { - if (!(path in this.localFiles)) { + const file = this.localFiles.get(path); + if (!file) { throw new Error(`File ${path} does not exist`); } - return this.localFiles[path]; + return file; } public async getFileSize(path: RelativePath): Promise { - if (!(path in this.localFiles)) { - throw new Error(`File ${path} does not exist`); - } - return this.localFiles[path].length; + return (await this.read(path)).length; } public async getModificationTime(path: RelativePath): Promise { - if (!(path in this.localFiles)) { + if (!this.localFiles.has(path)) { throw new Error(`File ${path} does not exist`); } return new Date(); } public async exists(path: RelativePath): Promise { - return path in this.localFiles; + return this.localFiles.has(path); } public async create( path: RelativePath, newContent: Uint8Array ): Promise { - if (path in this.localFiles) { + if (this.localFiles.has(path)) { throw new Error(`File ${path} already exists`); } - this.localFiles[path] = newContent; + this.localFiles.set(path, newContent); void this.client.syncer.syncLocallyCreatedFile(path, new Date()); } - public async createDirectory(path: RelativePath): Promise { + public async createDirectory(_path: RelativePath): Promise { // This doesn't mean anything in our virtual FS representation } @@ -84,13 +84,14 @@ export class MockClient implements FileSystemOperations { path: RelativePath, updater: (currentContent: string) => string ): Promise { - if (!(path in this.localFiles)) { + const file = this.localFiles.get(path); + if (!file) { throw new Error(`File ${path} does not exist`); } - const currentContent = new TextDecoder().decode(this.localFiles[path]); + const currentContent = new TextDecoder().decode(file); const newContent = updater(currentContent); const newContentUint8Array = new TextEncoder().encode(newContent); - this.localFiles[path] = newContentUint8Array; + this.localFiles.set(path, newContentUint8Array); void this.client.syncer.syncLocallyUpdatedFile({ relativePath: path, @@ -101,7 +102,7 @@ export class MockClient implements FileSystemOperations { } public async write(path: RelativePath, content: Uint8Array): Promise { - this.localFiles[path] = content; + this.localFiles.set(path, content); void this.client.syncer.syncLocallyUpdatedFile({ relativePath: path, @@ -110,7 +111,7 @@ export class MockClient implements FileSystemOperations { } public async delete(path: RelativePath): Promise { - delete this.localFiles[path]; + this.localFiles.delete(path); void this.client.syncer.syncLocallyDeletedFile(path); } @@ -118,13 +119,13 @@ export class MockClient implements FileSystemOperations { oldPath: RelativePath, newPath: RelativePath ): Promise { - if (!(oldPath in this.localFiles)) { + const file = this.localFiles.get(oldPath); + if (!file) { throw new Error(`File ${oldPath} does not exist`); } - - this.localFiles[newPath] = this.localFiles[oldPath]; + this.localFiles.set(newPath, file); if (oldPath !== newPath) { - delete this.localFiles[oldPath]; + this.localFiles.delete(oldPath); } void this.client.syncer.syncLocallyUpdatedFile({