From 37c101b7db2b6de24103f48776c5dc9fdd9d4d3a Mon Sep 17 00:00:00 2001 From: joan vicens Date: Fri, 29 Sep 2023 17:15:26 +0200 Subject: [PATCH 01/15] feat: add prefix to placeholders ids --- src/workers/sync-engine/BindingManager.ts | 2 -- .../controllers/AddController.ts | 10 ++++--- .../controllers/CallbackController.ts | 17 +++++++++--- .../controllers/DeleteController.ts | 2 +- .../controllers/RenameOrMoveController.ts | 2 +- .../placeholders/domain/FilePlaceholderId.ts | 24 +++++++++++++++++ .../domain/FolderPlaceholderId.ts | 27 +++++++++++++++++++ .../VirtualDrivePlaceholderCreator.ts | 10 +++++-- 8 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/workers/sync-engine/modules/placeholders/domain/FilePlaceholderId.ts create mode 100644 src/workers/sync-engine/modules/placeholders/domain/FolderPlaceholderId.ts diff --git a/src/workers/sync-engine/BindingManager.ts b/src/workers/sync-engine/BindingManager.ts index b97f51e89..61cc2b1e3 100644 --- a/src/workers/sync-engine/BindingManager.ts +++ b/src/workers/sync-engine/BindingManager.ts @@ -26,11 +26,9 @@ export class BindingsManager { controllers.delete .execute(contentsId) .then(() => { - Logger.debug('DELETE RESPONSE SUCCESSFUL'); callback(true); }) .catch((error: Error) => { - Logger.debug('DELETE RESPONSE NOT SUCCESSFUL'); Logger.error(error); callback(false); }); diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts index b24a4f505..e97bf1f51 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -2,8 +2,10 @@ import Logger from 'electron-log'; import { CallbackController } from './CallbackController'; import { rawPathIsFolder } from '../helpers/rawPathIsFolder'; import { FolderCreator } from '../../modules/folders/application/FolderCreator'; -import { MapObserver } from 'workers/sync-engine/modules/shared/domain/MapObserver'; -import { FileCreationOrchestrator } from 'workers/sync-engine/modules/boundaryBridge/application/FileCreationOrchestrator'; +import { MapObserver } from '../../modules/shared/domain/MapObserver'; +import { FileCreationOrchestrator } from '../../modules/boundaryBridge/application/FileCreationOrchestrator'; +import { createFolderPlaceholderId } from '../../modules/placeholders/domain/FolderPlaceholderId'; +import { createFilePlaceholderId } from '../../modules/placeholders/domain/FilePlaceholderId'; type Queue = Map void>; @@ -32,7 +34,7 @@ export class AddController extends CallbackController { ) => { try { const contentsId = await this.fileCreationOrchestrator.run(absolutePath); - return callback(true, contentsId); + return callback(true, createFilePlaceholderId(contentsId)); } catch (error: unknown) { Logger.error('Error when adding a file: ', error); callback(false, ''); @@ -60,7 +62,7 @@ export class AddController extends CallbackController { Logger.info('Creating folder', absolutePath); try { const folder = await this.folderCreator.run(absolutePath); - callback(true, folder.uuid); + callback(true, createFolderPlaceholderId(folder.uuid)); } catch (error: unknown) { Logger.error('Error creating a folder: ', error); callback(false, ''); diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts index 2c382b8f6..d6fcf6525 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts @@ -1,3 +1,6 @@ +import { isFilePlaceholderId } from '../../modules/placeholders/domain/FilePlaceholderId'; +import { isFolderPlaceholderId } from '../../modules/placeholders/domain/FolderPlaceholderId'; + export abstract class CallbackController { protected trim(id: string): string { return id.replace( @@ -6,12 +9,20 @@ export abstract class CallbackController { '' ); } - protected isContentsId(id: string): boolean { + + protected isFilePlaceholder(id: string): boolean { + // make sure the id is trimmed before comparing + // if it was already trimmed should not change its length + const trimmed = this.trim(id); + + return isFilePlaceholderId(trimmed); + } + + protected isFolderPlaceholder(id: string): boolean { // make sure the id is trimmed before comparing // if it was already trimmed should not change its length const trimmed = this.trim(id); - // TODO: need a better way to detect if its a file or a folder - return trimmed.length === 24; + return isFolderPlaceholderId(trimmed); } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index 796c89cf1..63711d27f 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -43,7 +43,7 @@ export class DeleteController extends CallbackController { async execute(contentsId: string) { const trimmedId = this.trim(contentsId); - if (this.isContentsId(trimmedId)) { + if (this.isFilePlaceholder(trimmedId)) { this.filesQueue.push(trimmedId); return; } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts index 19f4b873e..108d3cd4a 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts @@ -30,7 +30,7 @@ export class RenameOrMoveController extends CallbackController { const relative = this.filePathFromAbsolutePathCreator.run(absolutePath); - if (this.isContentsId(trimmedId)) { + if (this.isFilePlaceholder(trimmedId)) { await this.filePathUpdater.run(trimmedId, relative); return callback(true); } diff --git a/src/workers/sync-engine/modules/placeholders/domain/FilePlaceholderId.ts b/src/workers/sync-engine/modules/placeholders/domain/FilePlaceholderId.ts new file mode 100644 index 000000000..f78cec85c --- /dev/null +++ b/src/workers/sync-engine/modules/placeholders/domain/FilePlaceholderId.ts @@ -0,0 +1,24 @@ +export type FilePlaceholderIdPrefix = 'FILE:'; + +export type FilePlaceholderId = `${FilePlaceholderIdPrefix}${string}`; + +function typedCheck( + input: string, + prefix: FilePlaceholderIdPrefix = 'FILE:' +): input is FilePlaceholderId { + return input.startsWith(prefix); +} +export function isFilePlaceholderId(input: string): input is FilePlaceholderId { + return typedCheck(input); +} + +function typedCreate( + id: string, + prefix: FilePlaceholderIdPrefix = 'FILE:' +): FilePlaceholderId { + return (prefix + id) as FilePlaceholderId; +} + +export function createFilePlaceholderId(id: string): FilePlaceholderId { + return typedCreate(id); +} diff --git a/src/workers/sync-engine/modules/placeholders/domain/FolderPlaceholderId.ts b/src/workers/sync-engine/modules/placeholders/domain/FolderPlaceholderId.ts new file mode 100644 index 000000000..cacfb1070 --- /dev/null +++ b/src/workers/sync-engine/modules/placeholders/domain/FolderPlaceholderId.ts @@ -0,0 +1,27 @@ +export type FolderPlaceholderIdPrefix = 'FOLDER:'; + +export type FolderPlaceholderId = `${FolderPlaceholderIdPrefix}${string}`; + +function typedCheck( + input: string, + prefix: FolderPlaceholderIdPrefix = 'FOLDER:' +): input is FolderPlaceholderId { + return input.startsWith(prefix); +} + +export function isFolderPlaceholderId( + input: string +): input is FolderPlaceholderId { + return typedCheck(input); +} + +function typedCreate( + id: string, + prefix: FolderPlaceholderIdPrefix = 'FOLDER:' +): FolderPlaceholderId { + return (prefix + id) as FolderPlaceholderId; +} + +export function createFolderPlaceholderId(id: string): FolderPlaceholderId { + return typedCreate(id); +} diff --git a/src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts b/src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts index ba9ad56f6..e97888c17 100644 --- a/src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts +++ b/src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts @@ -2,6 +2,8 @@ import { VirtualDrive } from 'virtual-drive/dist'; import { File } from '../../files/domain/File'; import { Folder } from '../../folders/domain/Folder'; import { PlaceholderCreator } from '../domain/PlaceholderCreator'; +import { createFolderPlaceholderId } from '../domain/FolderPlaceholderId'; +import { createFilePlaceholderId } from '../domain/FilePlaceholderId'; export class VirtualDrivePlaceholderCreator implements PlaceholderCreator { constructor(private readonly drive: VirtualDrive) {} @@ -9,13 +11,17 @@ export class VirtualDrivePlaceholderCreator implements PlaceholderCreator { folder(folder: Folder): void { const folderPath = `${folder.path.value}/`; - this.drive.createItemByPath(folderPath, folder.uuid); + const placeholderId = createFolderPlaceholderId(folder.uuid); + + this.drive.createItemByPath(folderPath, placeholderId); } file(file: File): void { + const placeholderId = createFilePlaceholderId(file.contentsId); + this.drive.createItemByPath( file.path.value, - file.contentsId, + placeholderId, file.size, file.createdAt.getTime(), file.updatedAt.getTime() From 5cdc31492516a45124d02b34e5f7eeb4024d5906 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Mon, 2 Oct 2023 12:33:41 +0200 Subject: [PATCH 02/15] test: add folder creator test --- package.json | 3 + .../folders/application/FolderCreator.ts | 1 + .../modules/folders/domain/Folder.ts | 2 + .../test/application/FolderCreator.test.ts | 80 +++++++++++++++++++ .../folders/test/domain/FolderMother.ts | 12 +++ 5 files changed, 98 insertions(+) create mode 100644 src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts diff --git a/package.json b/package.json index 749aa15ee..7e090349c 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,9 @@ } }, "jest": { + "setupFilesAfterEnv": [ + "jest-extended/all" + ], "globals": { "ts-jest": { "tsconfig": "tsconfig.test.json" diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index 71adacecb..bcb87b59d 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -17,6 +17,7 @@ export class FolderCreator { const folderPath = this.folderPathFromAbsolutePathCreator.fromAbsolute( PlatformPathConverter.winToPosix(absolutePath) ); + this.ipc.send('FOLDER_CREATING', { name: folderPath.name(), }); diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index 703214f09..d5cc1e6ed 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -184,3 +184,5 @@ export class Folder extends AggregateRoot { return attributes; } } + +export const RootFolderName = '/' as const; diff --git a/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts new file mode 100644 index 000000000..998f911bd --- /dev/null +++ b/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts @@ -0,0 +1,80 @@ +import path from 'path'; +import { IpcRendererSyncEngineMock } from '../../../shared/test/__mock__/IpcRendererSyncEngineMock'; +import { FolderCreator } from '../../application/FolderCreator'; +import { FolderFinder } from '../../application/FolderFinder'; +import { FolderPathCreator } from '../../application/FolderPathCreator'; +import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; +import { FolderPath } from '../../domain/FolderPath'; +import { FolderMother } from '../domain/FolderMother'; + +describe('Folder Creator', () => { + let SUT: FolderCreator; + + let folderPathCreator: FolderPathCreator; + let repository: FolderRepositoryMock; + let folderFinder: FolderFinder; + let syncEngineIpc: IpcRendererSyncEngineMock; + + const BASE_FOLDER_ID = 'D:\\Users\\HalfBloodPrince\\InternxtDrive'; + + beforeEach(() => { + folderPathCreator = new FolderPathCreator(BASE_FOLDER_ID); + repository = new FolderRepositoryMock(); + folderFinder = new FolderFinder(repository); + syncEngineIpc = new IpcRendererSyncEngineMock(); + + SUT = new FolderCreator( + folderPathCreator, + repository, + folderFinder, + syncEngineIpc + ); + }); + + it('creates on a folder on the root folder', async () => { + const folderPath = path.join(BASE_FOLDER_ID, 'lily'); + const expectedPath = new FolderPath('\\lily'); + + const folder = FolderMother.root(); + + const spy = jest.spyOn(folderFinder, 'run').mockReturnValueOnce(folder); + + await SUT.run(folderPath); + + expect(spy).toBeCalledWith('/'); + expect(repository.mockCreate).toBeCalledWith(expectedPath, folder.id); + }); + + describe('Synchronization messages', () => { + it('sends the message FOLDER_CREATING', async () => { + const folderPath = path.join(BASE_FOLDER_ID, 'lily'); + + const folder = FolderMother.root(); + + jest.spyOn(folderFinder, 'run').mockReturnValueOnce(folder); + + await SUT.run(folderPath); + + expect(syncEngineIpc.sendMock).toBeCalledWith('FOLDER_CREATING', { + name: 'lily', + }); + expect(syncEngineIpc.sendMock).toHaveBeenCalledBefore( + repository.mockCreate + ); + }); + + it('sends the message FOLDER_CREATED', async () => { + const folderPath = path.join(BASE_FOLDER_ID, 'lily'); + + const folder = FolderMother.root(); + + jest.spyOn(folderFinder, 'run').mockReturnValueOnce(folder); + + await SUT.run(folderPath); + + expect(syncEngineIpc.sendMock).toBeCalledWith('FOLDER_CREATED', { + name: 'lily', + }); + }); + }); +}); diff --git a/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts b/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts index 18c642dfb..36d203721 100644 --- a/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts +++ b/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts @@ -77,4 +77,16 @@ export class FolderMother { status: FolderStatuses.TRASHED, }); } + + static root() { + return Folder.from({ + id: 2048, + uuid: FolderUuid.random().value, + path: '/', + parentId: null, + updatedAt: new Date().toISOString(), + createdAt: new Date().toISOString(), + status: FolderStatuses.EXISTS, + }); + } } From aab6417d54cfc15a3bdca17026089d82c1ba0dd1 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Mon, 2 Oct 2023 18:57:01 +0200 Subject: [PATCH 03/15] wip --- src/workers/sync-engine/BindingManager.ts | 15 +- .../callbacks-controllers/buildControllers.ts | 15 +- .../controllers/AddController.ts | 48 +++++-- .../controllers/CallbackController.ts | 4 + .../offline/OfflineRenameOrMoveController.ts | 34 +++++ .../executeControllerWithFallback.ts | 32 +++++ .../folders/FoldersContainer.ts | 8 ++ .../dependency-injection/folders/builder.ts | 41 +++++- .../Offline/OfflineFolderPathUpdater.ts | 0 .../application/FolderByPartialSearcher.ts | 10 ++ .../folders/application/FolderCreator.ts | 17 +-- .../Offline/OfflineFolderCreator.ts | 29 ++++ .../application/Offline/OfflineFolderMover.ts | 28 ++++ .../Offline/OfflineFolderPathUpdater.ts | 45 ++++++ .../Offline/OfflineFolderRenamer.ts | 15 ++ .../modules/folders/domain/OfflineFolder.ts | 135 ++++++++++++++++++ .../folders/domain/OfflineFolderRepository.ts | 7 + .../InMemoryOfflineFolderRepository.ts | 24 ++++ .../shared/domain/OfflineActionsQueue.ts | 10 ++ 19 files changed, 479 insertions(+), 38 deletions(-) create mode 100644 src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts create mode 100644 src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts create mode 100644 src/workers/sync-engine/modules/files/application/Offline/OfflineFolderPathUpdater.ts create mode 100644 src/workers/sync-engine/modules/folders/application/FolderByPartialSearcher.ts create mode 100644 src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts create mode 100644 src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderMover.ts create mode 100644 src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts create mode 100644 src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts create mode 100644 src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts create mode 100644 src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts create mode 100644 src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts create mode 100644 src/workers/sync-engine/modules/shared/domain/OfflineActionsQueue.ts diff --git a/src/workers/sync-engine/BindingManager.ts b/src/workers/sync-engine/BindingManager.ts index 61cc2b1e3..95e3708cc 100644 --- a/src/workers/sync-engine/BindingManager.ts +++ b/src/workers/sync-engine/BindingManager.ts @@ -1,6 +1,7 @@ import Logger from 'electron-log'; import { DependencyContainer } from './dependency-injection/DependencyContainer'; import { buildControllers } from './callbacks-controllers/buildControllers'; +import { executeControllerWithFallback } from './callbacks-controllers/middlewares/executeControllerWithFallback'; export class BindingsManager { private static readonly PROVIDER_NAME = 'Internxt'; @@ -41,11 +42,15 @@ export class BindingsManager { contentsId: string, callback: (response: boolean) => void ) => { - controllers.renameOrMoveFile.execute( - absolutePath, - contentsId, - callback - ); + const fn = executeControllerWithFallback({ + handler: controllers.renameOrMove.execute.bind( + controllers.renameOrMove + ), + fallback: controllers.offline.renameOrMove.execute.bind( + controllers.offline.renameOrMove + ), + }); + fn(absolutePath, contentsId, callback); }, notifyFileAddedCallback: ( absolutePath: string, diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index e383b7968..dfc05a0dd 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -3,11 +3,13 @@ import { AddController } from './controllers/AddController'; import { DeleteController } from './controllers/DeleteController'; import { DownloadFileController } from './controllers/DownloadFileController'; import { RenameOrMoveController } from './controllers/RenameOrMoveController'; +import { OfflineRenameOrMoveController } from './controllers/offline/OfflineRenameOrMoveController'; export function buildControllers(container: DependencyContainer) { const addFileController = new AddController( container.fileCreationOrchestrator, - container.folderCreator + container.folderCreator, + container.offline.folderCreator ); const deleteController = new DeleteController( @@ -15,7 +17,7 @@ export function buildControllers(container: DependencyContainer) { container.folderDeleter ); - const renameOrMoveFileController = new RenameOrMoveController( + const renameOrMoveController = new RenameOrMoveController( container.filePathFromAbsolutePathCreator, container.filePathUpdater, container.folderPathUpdater, @@ -28,10 +30,17 @@ export function buildControllers(container: DependencyContainer) { container.localRepositoryRefresher ); + const offlineRenameOrMoveController = new OfflineRenameOrMoveController( + container.offline.folderPathUpdater + ); + return { addFile: addFileController, - renameOrMoveFile: renameOrMoveFileController, + renameOrMove: renameOrMoveController, delete: deleteController, downloadFile: downloadFileController, + offline: { + renameOrMove: offlineRenameOrMoveController, + }, } as const; } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts index e97bf1f51..331491434 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -6,18 +6,29 @@ import { MapObserver } from '../../modules/shared/domain/MapObserver'; import { FileCreationOrchestrator } from '../../modules/boundaryBridge/application/FileCreationOrchestrator'; import { createFolderPlaceholderId } from '../../modules/placeholders/domain/FolderPlaceholderId'; import { createFilePlaceholderId } from '../../modules/placeholders/domain/FilePlaceholderId'; +import { OfflineFolderCreator } from '../../modules/folders/application/Offline/OfflineFolderCreator'; +import { OfflineFolder } from 'workers/sync-engine/modules/folders/domain/OfflineFolder'; -type Queue = Map void>; +type FileCreationQueue = Map< + string, + (acknowledge: boolean, id: string) => void +>; +type FolderCreationQueue = Map< + OfflineFolder, + (acknowledge: boolean, id: string) => void +>; +type CreationCallback = (acknowledge: boolean, id: string) => void; export class AddController extends CallbackController { - private readonly filesQueue: Queue; - private readonly foldersQueue: Queue; + private readonly filesQueue: FileCreationQueue; + private readonly foldersQueue: FolderCreationQueue; private readonly observer: MapObserver; constructor( private readonly fileCreationOrchestrator: FileCreationOrchestrator, - private readonly folderCreator: FolderCreator + private readonly folderCreator: FolderCreator, + private readonly offlineFolderCreator: OfflineFolderCreator ) { super(); @@ -34,7 +45,7 @@ export class AddController extends CallbackController { ) => { try { const contentsId = await this.fileCreationOrchestrator.run(absolutePath); - return callback(true, createFilePlaceholderId(contentsId)); + return callback(true, contentsId); } catch (error: unknown) { Logger.error('Error when adding a file: ', error); callback(false, ''); @@ -56,35 +67,46 @@ export class AddController extends CallbackController { }; private createFolder = async ( - absolutePath: string, + offlineFolder: OfflineFolder, callback: (acknowledge: boolean, id: string) => void ) => { - Logger.info('Creating folder', absolutePath); + Logger.info('Creating folder', offlineFolder); try { - const folder = await this.folderCreator.run(absolutePath); - callback(true, createFolderPlaceholderId(folder.uuid)); + await this.folderCreator.run(offlineFolder); } catch (error: unknown) { Logger.error('Error creating a folder: ', error); callback(false, ''); } finally { - this.foldersQueue.delete(absolutePath); + this.foldersQueue.delete(offlineFolder); } }; + private enqueueFolder = ( + absolutePath: string, + callback: CreationCallback + ) => { + const offlineFolder = this.offlineFolderCreator.run(absolutePath); + callback(true, offlineFolder.uuid); + this.foldersQueue.set(offlineFolder, () => {}); + }; + async execute( absolutePath: string, - callback: (acknowledge: boolean, id: string) => void + callback: CreationCallback ): Promise { if (rawPathIsFolder(absolutePath)) { - this.foldersQueue.set(absolutePath, callback); + this.enqueueFolder(absolutePath, callback); await this.createFolders(); return; } - Logger.debug('File is going to be queued: ', absolutePath); this.filesQueue.set(absolutePath, callback); + if (this.foldersQueue.size === 0) { + Logger.debug('File is not going to be queued. Creating...', absolutePath); this.createFiles(); + } else { + Logger.debug('File has been queued: ', absolutePath); } } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts index d6fcf6525..ee92b1a5a 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts @@ -15,6 +15,8 @@ export abstract class CallbackController { // if it was already trimmed should not change its length const trimmed = this.trim(id); + return trimmed.length === 24; + return isFilePlaceholderId(trimmed); } @@ -23,6 +25,8 @@ export abstract class CallbackController { // if it was already trimmed should not change its length const trimmed = this.trim(id); + return trimmed.length > 24; + return isFolderPlaceholderId(trimmed); } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts new file mode 100644 index 000000000..0c21ee445 --- /dev/null +++ b/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts @@ -0,0 +1,34 @@ +import { OfflineFolderPathUpdater } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater'; +import { CallbackController } from '../CallbackController'; +import Logger from 'electron-log'; + +export class OfflineRenameOrMoveController extends CallbackController { + constructor(private readonly folderPathUpdater: OfflineFolderPathUpdater) { + super(); + } + + async execute( + absolutePath: string, + placeholderId: string, + callback: (response: boolean) => void + ) { + Logger.warn('Inside the fallback'); + const trimmedId = this.trim(placeholderId); + + try { + if (absolutePath.startsWith('\\$Recycle.Bin')) { + return callback(false); + } + + if (this.isFilePlaceholder(trimmedId)) { + return callback(false); + } + Logger.debug('absolute path', absolutePath); + await this.folderPathUpdater.run(trimmedId, absolutePath); + callback(true); + } catch (error: unknown) { + Logger.error(error); + callback(false); + } + } +} diff --git a/src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts b/src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts new file mode 100644 index 000000000..d51d179fe --- /dev/null +++ b/src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts @@ -0,0 +1,32 @@ +import Logger from 'electron-log'; + +export const executeControllerWithFallback = + void>({ + handler, + fallback, + }: { + handler: Action; + fallback: Action; + }) => + ( + absolutePath: string, + placeholderId: string, + callback: (response: boolean) => void + ) => { + Logger.warn('Executing'); + try { + handler(absolutePath, placeholderId, (response: boolean) => { + if (!response) { + Logger.warn('Default handler failed, running fallback'); + fallback(absolutePath, placeholderId, callback); + return; + } + + Logger.warn('Default handler success'); + callback(true); + }); + } catch (error: unknown) { + Logger.error(error); + callback(false); + } + }; diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index 2dee3b7a3..65a4b043d 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -5,6 +5,9 @@ import { FolderPathCreator } from '../../modules/folders/application/FolderPathC import { FolderPathUpdater } from '../../modules/folders/application/FolderPathUpdater'; import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; import { AllParentFoldersStatusIsExists } from '../../modules/folders/application/AllParentFoldersStatusIsExists'; +import { FolderByPartialSearcher } from '../../modules/folders/application/FolderByPartialSearcher'; +import { OfflineFolderCreator } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator'; +import { OfflineFolderPathUpdater } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater'; export interface FoldersContainer { folderCreator: FolderCreator; @@ -14,4 +17,9 @@ export interface FoldersContainer { folderDeleter: FolderDeleter; allParentFoldersStatusIsExists: AllParentFoldersStatusIsExists; folderPathUpdater: FolderPathUpdater; + folderByPartialSearcher: FolderByPartialSearcher; + offline: { + folderCreator: OfflineFolderCreator; + folderPathUpdater: OfflineFolderPathUpdater; + }; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index c4a1542e7..6d81a4f76 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -14,6 +14,12 @@ import { FolderPathUpdater } from 'workers/sync-engine/modules/folders/applicati import { FolderMover } from 'workers/sync-engine/modules/folders/application/FolderMover'; import { FolderRenamer } from 'workers/sync-engine/modules/folders/application/FolderRenamer'; import { PlaceholderContainer } from '../placeholders/PlaceholdersContainer'; +import { FolderByPartialSearcher } from 'workers/sync-engine/modules/folders/application/FolderByPartialSearcher'; +import { InMemoryOfflineFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository'; +import { OfflineFolderCreator } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator'; +import { OfflineFolderPathUpdater } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater'; +import { OfflineFolderMover } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderMover'; +import { OfflineFolderRenamer } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer'; export async function buildFoldersContainer( placeholdersContainer: PlaceholderContainer @@ -30,9 +36,7 @@ export async function buildFoldersContainer( ); await repository.init(); - const folderPathFromAbsolutePathCreator = new FolderPathCreator( - rootFolderPath - ); + const folderPathCreator = new FolderPathCreator(rootFolderPath); const folderFinder = new FolderFinder(repository); @@ -49,7 +53,6 @@ export async function buildFoldersContainer( ); const folderCreator = new FolderCreator( - folderPathFromAbsolutePathCreator, repository, folderFinder, ipcRendererSyncEngine @@ -58,20 +61,46 @@ export async function buildFoldersContainer( const folderMover = new FolderMover(repository, folderFinder); const folderRenamer = new FolderRenamer(repository, ipcRendererSyncEngine); + const folderByPartialSearcher = new FolderByPartialSearcher(repository); + const folderPathUpdater = new FolderPathUpdater( repository, - folderPathFromAbsolutePathCreator, + folderPathCreator, folderMover, folderRenamer ); + const offlineRepository = new InMemoryOfflineFolderRepository(); + const offlineFolderCreator = new OfflineFolderCreator( + folderPathCreator, + folderFinder, + offlineRepository + ); + + const offlineFolderMover = new OfflineFolderMover( + offlineRepository, + folderFinder + ); + const offlineFolderRenamer = new OfflineFolderRenamer(offlineRepository); + const offlineFolderPathUpdater = new OfflineFolderPathUpdater( + offlineRepository, + folderPathCreator, + offlineFolderMover, + offlineFolderRenamer + ); + return { folderCreator, folderFinder, - folderPathFromAbsolutePathCreator, + folderPathFromAbsolutePathCreator: folderPathCreator, folderSearcher, folderDeleter, allParentFoldersStatusIsExists: allParentFoldersStatusIsExists, folderPathUpdater, + folderByPartialSearcher, + offline: { + folderCreator: offlineFolderCreator, + folderPathUpdater: offlineFolderPathUpdater, + }, }; } diff --git a/src/workers/sync-engine/modules/files/application/Offline/OfflineFolderPathUpdater.ts b/src/workers/sync-engine/modules/files/application/Offline/OfflineFolderPathUpdater.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/workers/sync-engine/modules/folders/application/FolderByPartialSearcher.ts b/src/workers/sync-engine/modules/folders/application/FolderByPartialSearcher.ts new file mode 100644 index 000000000..634d4573a --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/FolderByPartialSearcher.ts @@ -0,0 +1,10 @@ +import { Folder, FolderAttributes } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; + +export class FolderByPartialSearcher { + constructor(private readonly repository: FolderRepository) {} + + run(partial: Partial): Folder | undefined { + return this.repository.searchByPartial(partial); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index bcb87b59d..25fa71da4 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -2,34 +2,29 @@ import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; +import { OfflineFolder } from '../domain/OfflineFolder'; import { FolderFinder } from './FolderFinder'; -import { FolderPathCreator } from './FolderPathCreator'; export class FolderCreator { constructor( - private readonly folderPathFromAbsolutePathCreator: FolderPathCreator, private readonly repository: FolderRepository, private readonly folderFinder: FolderFinder, private readonly ipc: SyncEngineIpc ) {} - async run(absolutePath: string): Promise { - const folderPath = this.folderPathFromAbsolutePathCreator.fromAbsolute( - PlatformPathConverter.winToPosix(absolutePath) - ); - + async run(offlineFolder: OfflineFolder): Promise { this.ipc.send('FOLDER_CREATING', { - name: folderPath.name(), + name: offlineFolder.name, }); const parent = this.folderFinder.run( - PlatformPathConverter.winToPosix(folderPath.dirname()) + PlatformPathConverter.winToPosix(offlineFolder.dirname) ); - const folder = await this.repository.create(folderPath, parent.id); + const folder = await this.repository.create(offlineFolder.path, parent.id); this.ipc.send('FOLDER_CREATED', { - name: folderPath.name(), + name: offlineFolder.name, }); return folder; diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts new file mode 100644 index 000000000..d14b3e87c --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts @@ -0,0 +1,29 @@ +import { PlatformPathConverter } from '../../../shared/test/helpers/PlatformPathConverter'; +import { OfflineFolder } from '../../domain/OfflineFolder'; +import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; +import { FolderFinder } from '../FolderFinder'; +import { FolderPathCreator } from '../FolderPathCreator'; + +export class OfflineFolderCreator { + constructor( + private readonly pathCreator: FolderPathCreator, + private readonly folderFinder: FolderFinder, + private readonly offlineRepository: OfflineFolderRepository + ) {} + + run(absolutePath: string): OfflineFolder { + const folderPath = this.pathCreator.fromAbsolute( + PlatformPathConverter.winToPosix(absolutePath) + ); + + const parent = this.folderFinder.run( + PlatformPathConverter.winToPosix(folderPath.dirname()) + ); + + const folder = OfflineFolder.create(folderPath, parent.id); + + this.offlineRepository.update(folder); + + return folder; + } +} diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderMover.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderMover.ts new file mode 100644 index 000000000..53358d73d --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderMover.ts @@ -0,0 +1,28 @@ +import { FolderPath } from '../../domain/FolderPath'; +import { OfflineFolder } from '../../domain/OfflineFolder'; +import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; +import { ActionNotPermittedError } from '../../domain/errors/ActionNotPermittedError'; +import { FolderFinder } from '../FolderFinder'; + +export class OfflineFolderMover { + constructor( + private readonly offlineFolderRepository: OfflineFolderRepository, + private readonly folderFinder: FolderFinder + ) {} + + async run(folder: OfflineFolder, destination: FolderPath) { + const resultFolder = this.offlineFolderRepository.getByPath( + destination.value + ); + const shouldBeMerge = resultFolder !== undefined; + + if (shouldBeMerge) { + throw new ActionNotPermittedError('overwrite'); + } + + const destinationFolder = this.folderFinder.run(destination.dirname()); + + folder.moveTo(destinationFolder); + this.offlineFolderRepository.update(folder); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts new file mode 100644 index 000000000..afe02a508 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts @@ -0,0 +1,45 @@ +import path from 'path'; +import { OfflineFolder } from '../../domain/OfflineFolder'; +import { FolderPathCreator } from '../FolderPathCreator'; +import { ActionNotPermittedError } from '../../domain/errors/ActionNotPermittedError'; +import { OfflineFolderMover } from './OfflineFolderMover'; +import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; +import { OfflineFolderRenamer } from './OfflineFolderRenamer'; + +export class OfflineFolderPathUpdater { + constructor( + private readonly offlineFoldersRepository: OfflineFolderRepository, + private readonly pathCreator: FolderPathCreator, + private readonly offlineFolderMover: OfflineFolderMover, + private readonly offlineFolderRenamer: OfflineFolderRenamer + ) {} + + async run(uuid: OfflineFolder['uuid'], absolutePath: string) { + const normalized = path.normalize(absolutePath); + + const folder = this.offlineFoldersRepository.getByUuid(uuid); + + if (!folder) { + throw new Error(`Folder ${uuid} not found in offline folders`); + } + + const desiredPath = this.pathCreator.fromAbsolute(normalized); + + const dirnameChanged = folder.dirname !== desiredPath.dirname(); + const nameChanged = folder.name !== desiredPath.name(); + + if (dirnameChanged && nameChanged) { + throw new ActionNotPermittedError('Move and rename (at the same time)'); + } + + if (dirnameChanged) { + return await this.offlineFolderMover.run(folder, desiredPath); + } + + if (nameChanged) { + return this.offlineFolderRenamer.run(folder, desiredPath); + } + + throw new Error('No path change detected for folder path update'); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts new file mode 100644 index 000000000..b17f8891b --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts @@ -0,0 +1,15 @@ +import { FolderPath } from '../../domain/FolderPath'; +import { OfflineFolder } from '../../domain/OfflineFolder'; +import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; + +import Logger from 'electron-log'; + +export class OfflineFolderRenamer { + constructor(private readonly offlineFiles: OfflineFolderRepository) {} + + run(folder: OfflineFolder, destination: FolderPath) { + Logger.debug('RENAME TO ', destination); + folder.rename(destination); + this.offlineFiles.update(folder); + } +} diff --git a/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts b/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts new file mode 100644 index 000000000..978197ca0 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts @@ -0,0 +1,135 @@ +import { Primitives } from 'shared/types/Primitives'; +import { AggregateRoot } from '../../shared/domain/AggregateRoot'; +import { FolderPath } from './FolderPath'; +import { FolderStatus, FolderStatuses } from './FolderStatus'; +import { FolderUuid } from './FolderUuid'; +import { Folder } from './Folder'; + +export type OfflineFolderAttributes = { + uuid: string; + parentId: number; + path: string; + updatedAt: string; + createdAt: string; + status: string; +}; + +// type FunctionKeys = { +// [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; +// }[keyof T]; + +// type Action = [keyof Folder, Parameters]>]; + +export class OfflineFolder extends AggregateRoot { + private constructor( + private _uuid: FolderUuid, + private _path: FolderPath, + private _parentId: number, + public createdAt: Date, + public updatedAt: Date, + private _status: FolderStatus + ) { + super(); + } + + public get uuid(): string { + return this._uuid.value; + } + + public get path() { + return this._path; + } + + public get name() { + return this._path.name(); + } + + public get dirname() { + return this._path.dirname(); + } + + public get parentId() { + return this._parentId; + } + + public get status() { + return this._status; + } + + public get size() { + // Currently we cannot acquire the folder size. + return 0; + } + + static from(attributes: OfflineFolderAttributes): OfflineFolder { + return new OfflineFolder( + new FolderUuid(attributes.uuid), + new FolderPath(attributes.path), + attributes.parentId, + new Date(attributes.updatedAt), + new Date(attributes.createdAt), + FolderStatus.fromValue(attributes.status) + ); + } + + static create(path: FolderPath, parentId: number): OfflineFolder { + return new OfflineFolder( + FolderUuid.random(), + path, + parentId, + new Date(), + new Date(), + FolderStatus.Exists + ); + } + + moveTo(destinationFolder: Folder) { + this._parentId = destinationFolder.id; + } + + rename(destination: FolderPath) { + if (this._path.hasSameName(destination)) { + throw new Error('Cannot rename a folder to the same name'); + } + + this._path = this._path.updateName(destination.name()); + } + + isFolder(): this is OfflineFolder { + return true; + } + + isFile(): this is File { + return false; + } + + hasStatus(status: FolderStatuses): boolean { + return this._status.value === status; + } + + toPrimitives(): Record { + const attributes: OfflineFolderAttributes = { + uuid: this.uuid, + parentId: this._parentId, + path: this._path.value, + updatedAt: this.updatedAt.toISOString(), + createdAt: this.createdAt.toISOString(), + status: this.status.value, + }; + + return attributes; + } + + attributes(): OfflineFolderAttributes { + const attributes: OfflineFolderAttributes = { + uuid: this.uuid, + parentId: this._parentId, + path: this._path.value, + updatedAt: this.updatedAt.toISOString(), + createdAt: this.createdAt.toISOString(), + status: this.status.value, + }; + + return attributes; + } +} diff --git a/src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts b/src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts new file mode 100644 index 000000000..24ff0a0e7 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts @@ -0,0 +1,7 @@ +import { OfflineFolder } from './OfflineFolder'; + +export interface OfflineFolderRepository { + getByUuid(uuid: OfflineFolder['uuid']): OfflineFolder | undefined; + getByPath(path: string): OfflineFolder | undefined; + update(folder: OfflineFolder): void; +} diff --git a/src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts new file mode 100644 index 000000000..8aa74556e --- /dev/null +++ b/src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts @@ -0,0 +1,24 @@ +import { OfflineFolder } from '../domain/OfflineFolder'; +import { OfflineFolderRepository } from '../domain/OfflineFolderRepository'; +import Logger from 'electron-log'; + +export class InMemoryOfflineFolderRepository + implements OfflineFolderRepository +{ + private foldersByPath: Record = {}; + private foldersByUuid: Record = {}; + + getByUuid(uuid: string): OfflineFolder | undefined { + return this.foldersByUuid[uuid]; + } + + getByPath(path: string): OfflineFolder | undefined { + return this.foldersByPath[path]; + } + + update(folder: OfflineFolder): void { + Logger.debug('OFFLINE FOLDER UPDATED:', folder.attributes()); + this.foldersByPath[folder.path.value] = folder; + this.foldersByUuid[folder.uuid] = folder; + } +} diff --git a/src/workers/sync-engine/modules/shared/domain/OfflineActionsQueue.ts b/src/workers/sync-engine/modules/shared/domain/OfflineActionsQueue.ts new file mode 100644 index 000000000..cd3f54b86 --- /dev/null +++ b/src/workers/sync-engine/modules/shared/domain/OfflineActionsQueue.ts @@ -0,0 +1,10 @@ +export type OfflineAction = () => Promise; +export type OfflineActions = Array; + +export class OfflineActionsQueue { + private queue: OfflineActions = []; + + add(action: OfflineAction): void { + this.queue.push(action); + } +} From 28c53d52ed4d564d0d0d42aba2abd4bc771f0717 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 3 Oct 2023 12:05:25 +0200 Subject: [PATCH 04/15] wip: synchronize offline modifications --- .../controllers/AddController.ts | 2 - .../DependencyContainerFactory.ts | 1 + .../dependency-injection/common/eventBus.ts | 5 ++- .../files/FilesContainer.ts | 2 - .../dependency-injection/files/builder.ts | 6 --- .../folders/FoldersContainer.ts | 12 ++++-- .../dependency-injection/folders/builder.ts | 41 ++++++++++++------ .../CreateFilePlaceholderEmitter.ts | 12 ------ .../folders/application/FolderCreator.ts | 7 +++- .../SynchronizeOfflineModifications.ts | 42 +++++++++++++++++++ ...nizeOfflineModificationsOnFolderCreated.ts | 19 +++++++++ .../modules/folders/domain/Folder.ts | 12 +++++- .../domain/events/FolderCreatedDomainEvent.ts | 18 ++++++++ .../modules/shared/domain/DomainEvent.ts | 2 +- .../shared/infrastructure/NodeJsEventBus.ts | 2 +- 15 files changed, 138 insertions(+), 45 deletions(-) delete mode 100644 src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts create mode 100644 src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts create mode 100644 src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts create mode 100644 src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts index 331491434..7fda63859 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -4,8 +4,6 @@ import { rawPathIsFolder } from '../helpers/rawPathIsFolder'; import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { MapObserver } from '../../modules/shared/domain/MapObserver'; import { FileCreationOrchestrator } from '../../modules/boundaryBridge/application/FileCreationOrchestrator'; -import { createFolderPlaceholderId } from '../../modules/placeholders/domain/FolderPlaceholderId'; -import { createFilePlaceholderId } from '../../modules/placeholders/domain/FilePlaceholderId'; import { OfflineFolderCreator } from '../../modules/folders/application/Offline/OfflineFolderCreator'; import { OfflineFolder } from 'workers/sync-engine/modules/folders/domain/OfflineFolder'; diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index 977fed4aa..3c4be66fe 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -15,6 +15,7 @@ export class DependencyContainerFactory { static readonly subscribers: Array = [ 'createFilePlaceholderOnDeletionFailed', + 'synchronizeOfflineModificationsOnFolderCreated', ]; eventSubscribers( diff --git a/src/workers/sync-engine/dependency-injection/common/eventBus.ts b/src/workers/sync-engine/dependency-injection/common/eventBus.ts index be4939af9..e128cb87f 100644 --- a/src/workers/sync-engine/dependency-injection/common/eventBus.ts +++ b/src/workers/sync-engine/dependency-injection/common/eventBus.ts @@ -1,9 +1,10 @@ -import { NodeJsEventBus } from 'workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus'; +import { EventBus } from '../../modules/shared/domain/WebdavServerEventBus'; +import { NodeJsEventBus } from '../../modules/shared/infrastructure/NodeJsEventBus'; export class DependencyInjectionEventBus { private static _bus: NodeJsEventBus; - static get bus(): NodeJsEventBus { + static get bus(): EventBus { if (DependencyInjectionEventBus._bus) { return DependencyInjectionEventBus._bus; } diff --git a/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts b/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts index 54e105acd..b23b53e74 100644 --- a/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts +++ b/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts @@ -1,5 +1,4 @@ import { CreateFilePlaceholderOnDeletionFailed } from '../../modules/files/application/CreateFilePlaceholderOnDeletionFailed'; -import { CreateFilePlaceholderEmitter } from '../../modules/files/application/CreateFilePlaceholderEmitter'; import { FileByPartialSearcher } from '../../modules/files/application/FileByPartialSearcher'; import { FileCreator } from '../../modules/files/application/FileCreator'; import { FileDeleter } from '../../modules/files/application/FileDeleter'; @@ -19,7 +18,6 @@ export interface FilesContainer { fileCreator: FileCreator; filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator; fileSearcher: FileSearcher; - createFilePlaceholderEmitter: CreateFilePlaceholderEmitter; filePlaceholderCreatorFromContentsId: FilePlaceholderCreatorFromContentsId; createFilePlaceholderOnDeletionFailed: CreateFilePlaceholderOnDeletionFailed; } diff --git a/src/workers/sync-engine/dependency-injection/files/builder.ts b/src/workers/sync-engine/dependency-injection/files/builder.ts index 3143ec61c..c6cf8f4cc 100644 --- a/src/workers/sync-engine/dependency-injection/files/builder.ts +++ b/src/workers/sync-engine/dependency-injection/files/builder.ts @@ -1,4 +1,3 @@ -import { CreateFilePlaceholderEmitter } from 'workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter'; import { CreateFilePlaceholderOnDeletionFailed } from 'workers/sync-engine/modules/files/application/CreateFilePlaceholderOnDeletionFailed'; import { FilePlaceholderCreatorFromContentsId } from 'workers/sync-engine/modules/files/application/FilePlaceholderCreatorFromContentsId'; import crypt from '../../../utils/crypt'; @@ -81,10 +80,6 @@ export async function buildFilesContainer( const fileSearcher = new FileSearcher(fileRepository); - const createFilePlaceholderEmitter = new CreateFilePlaceholderEmitter( - eventBus - ); - const filePlaceholderCreatorFromContentsId = new FilePlaceholderCreatorFromContentsId( fileFinderByContentsId, @@ -105,7 +100,6 @@ export async function buildFilesContainer( fileCreator, filePathFromAbsolutePathCreator, fileSearcher, - createFilePlaceholderEmitter: createFilePlaceholderEmitter, filePlaceholderCreatorFromContentsId: filePlaceholderCreatorFromContentsId, createFilePlaceholderOnDeletionFailed: createFilePlaceholderOnDeletionFailed, diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index 65a4b043d..5dd040e8f 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -1,13 +1,15 @@ +import { AllParentFoldersStatusIsExists } from '../../modules/folders/application/AllParentFoldersStatusIsExists'; +import { FolderByPartialSearcher } from '../../modules/folders/application/FolderByPartialSearcher'; import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { FolderDeleter } from '../../modules/folders/application/FolderDeleter'; import { FolderFinder } from '../../modules/folders/application/FolderFinder'; import { FolderPathCreator } from '../../modules/folders/application/FolderPathCreator'; import { FolderPathUpdater } from '../../modules/folders/application/FolderPathUpdater'; import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; -import { AllParentFoldersStatusIsExists } from '../../modules/folders/application/AllParentFoldersStatusIsExists'; -import { FolderByPartialSearcher } from '../../modules/folders/application/FolderByPartialSearcher'; -import { OfflineFolderCreator } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator'; -import { OfflineFolderPathUpdater } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater'; +import { OfflineFolderCreator } from '../../modules/folders/application/Offline/OfflineFolderCreator'; +import { OfflineFolderPathUpdater } from '../../modules/folders/application/Offline/OfflineFolderPathUpdater'; +import { SynchronizeOfflineModifications } from '../../modules/folders/application/SynchronizeOfflineModifications'; +import { SynchronizeOfflineModificationsOnFolderCreated } from '../../modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated'; export interface FoldersContainer { folderCreator: FolderCreator; @@ -18,8 +20,10 @@ export interface FoldersContainer { allParentFoldersStatusIsExists: AllParentFoldersStatusIsExists; folderPathUpdater: FolderPathUpdater; folderByPartialSearcher: FolderByPartialSearcher; + synchronizeOfflineModificationsOnFolderCreated: SynchronizeOfflineModificationsOnFolderCreated; offline: { folderCreator: OfflineFolderCreator; folderPathUpdater: OfflineFolderPathUpdater; + synchronizeOfflineModifications: SynchronizeOfflineModifications; }; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 6d81a4f76..565709b7c 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -1,25 +1,28 @@ +import { AllParentFoldersStatusIsExists } from 'workers/sync-engine/modules/folders/application/AllParentFoldersStatusIsExists'; +import { FolderByPartialSearcher } from 'workers/sync-engine/modules/folders/application/FolderByPartialSearcher'; import { FolderCreator } from 'workers/sync-engine/modules/folders/application/FolderCreator'; import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; import { FolderFinder } from 'workers/sync-engine/modules/folders/application/FolderFinder'; +import { FolderMover } from 'workers/sync-engine/modules/folders/application/FolderMover'; import { FolderPathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathCreator'; +import { FolderPathUpdater } from 'workers/sync-engine/modules/folders/application/FolderPathUpdater'; +import { FolderRenamer } from 'workers/sync-engine/modules/folders/application/FolderRenamer'; import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; -import { AllParentFoldersStatusIsExists } from 'workers/sync-engine/modules/folders/application/AllParentFoldersStatusIsExists'; +import { OfflineFolderCreator } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator'; +import { OfflineFolderMover } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderMover'; +import { OfflineFolderPathUpdater } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater'; +import { OfflineFolderRenamer } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer'; import { HttpFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository'; +import { InMemoryOfflineFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository'; import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; import { DependencyInjectionHttpClientsProvider } from '../common/clients'; +import { DependencyInjectionEventBus } from '../common/eventBus'; import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { DependencyInjectionTraverserProvider } from '../common/traverser'; -import { FoldersContainer } from './FoldersContainer'; -import { FolderPathUpdater } from 'workers/sync-engine/modules/folders/application/FolderPathUpdater'; -import { FolderMover } from 'workers/sync-engine/modules/folders/application/FolderMover'; -import { FolderRenamer } from 'workers/sync-engine/modules/folders/application/FolderRenamer'; import { PlaceholderContainer } from '../placeholders/PlaceholdersContainer'; -import { FolderByPartialSearcher } from 'workers/sync-engine/modules/folders/application/FolderByPartialSearcher'; -import { InMemoryOfflineFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository'; -import { OfflineFolderCreator } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator'; -import { OfflineFolderPathUpdater } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater'; -import { OfflineFolderMover } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderMover'; -import { OfflineFolderRenamer } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer'; +import { FoldersContainer } from './FoldersContainer'; +import { SynchronizeOfflineModifications } from 'workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications'; +import { SynchronizeOfflineModificationsOnFolderCreated } from 'workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated'; export async function buildFoldersContainer( placeholdersContainer: PlaceholderContainer @@ -27,6 +30,7 @@ export async function buildFoldersContainer( const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); const rootFolderPath = DependencyInjectionLocalRootFolderPath.get(); + const eventBus = DependencyInjectionEventBus.bus; const repository = new HttpFolderRepository( clients.drive, @@ -55,7 +59,8 @@ export async function buildFoldersContainer( const folderCreator = new FolderCreator( repository, folderFinder, - ipcRendererSyncEngine + ipcRendererSyncEngine, + eventBus ); const folderMover = new FolderMover(repository, folderFinder); @@ -88,6 +93,16 @@ export async function buildFoldersContainer( offlineFolderMover, offlineFolderRenamer ); + const synchronizeOfflineModifications = new SynchronizeOfflineModifications( + offlineRepository, + repository, + folderRenamer + ); + + const synchronizeOfflineModificationsOnFolderCreated = + new SynchronizeOfflineModificationsOnFolderCreated( + synchronizeOfflineModifications + ); return { folderCreator, @@ -98,9 +113,11 @@ export async function buildFoldersContainer( allParentFoldersStatusIsExists: allParentFoldersStatusIsExists, folderPathUpdater, folderByPartialSearcher, + synchronizeOfflineModificationsOnFolderCreated, offline: { folderCreator: offlineFolderCreator, folderPathUpdater: offlineFolderPathUpdater, + synchronizeOfflineModifications, }, }; } diff --git a/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts b/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts deleted file mode 100644 index f9453f865..000000000 --- a/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NodeJsEventBus } from '../../shared/infrastructure/NodeJsEventBus'; -import { File } from '../domain/File'; - -export class CreateFilePlaceholderEmitter { - private static readonly EVENT_NAME = 'PLACEHOLDER:CREATE:FILE'; - - constructor(private readonly eventBus: NodeJsEventBus) {} - - emit(contentsId: File['contentsId']) { - this.eventBus.emit(CreateFilePlaceholderEmitter.EVENT_NAME, { contentsId }); - } -} diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index 25fa71da4..34d20c6a4 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -1,4 +1,5 @@ import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; +import { EventBus } from '../../shared/domain/WebdavServerEventBus'; import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; @@ -9,7 +10,8 @@ export class FolderCreator { constructor( private readonly repository: FolderRepository, private readonly folderFinder: FolderFinder, - private readonly ipc: SyncEngineIpc + private readonly ipc: SyncEngineIpc, + private readonly eventBus: EventBus ) {} async run(offlineFolder: OfflineFolder): Promise { @@ -23,6 +25,9 @@ export class FolderCreator { const folder = await this.repository.create(offlineFolder.path, parent.id); + const events = folder.pullDomainEvents(); + this.eventBus.publish(events); + this.ipc.send('FOLDER_CREATED', { name: offlineFolder.name, }); diff --git a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts new file mode 100644 index 000000000..1594b739b --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts @@ -0,0 +1,42 @@ +import { Folder } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; +import { OfflineFolderRepository } from '../domain/OfflineFolderRepository'; +import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; +import { FolderRenamer } from './FolderRenamer'; +import Logger from 'electron-log'; + +export class SynchronizeOfflineModifications { + constructor( + private readonly offlineRepository: OfflineFolderRepository, + private readonly repository: FolderRepository, + private readonly renamer: FolderRenamer + ) {} + + async run(uuid: Folder['uuid']) { + Logger.debug('Synchronize potential offline changes'); + const offlineFolder = this.offlineRepository.getByUuid(uuid); + const folder = this.repository.searchByPartial({ uuid }); + + if (!offlineFolder) { + Logger.debug(`There is no offline folder with ${uuid}`); + return; + } + + if (!folder) { + Logger.debug('There is no folder with ', uuid); + throw new FolderNotFoundError(uuid); + } + + if (offlineFolder.name === folder.name) { + Logger.debug( + 'Offline and online folder have the same name: ', + folder.name + ); + return; + } + + Logger.debug('Updating the folder with path: ', offlineFolder.path); + await this.renamer.run(folder, offlineFolder.path); + Logger.debug('Folder updated with the path: ', offlineFolder.path); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts new file mode 100644 index 000000000..d60a3d2a6 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts @@ -0,0 +1,19 @@ +import { DomainEventClass } from '../../shared/domain/DomainEvent'; +import { WebdavDomainEventSubscriber } from '../../shared/domain/WebdavDomainEventSubscriber'; +import { FolderCreatedDomainEvent } from '../domain/events/FolderCreatedDomainEvent'; +import { SynchronizeOfflineModifications } from './SynchronizeOfflineModifications'; + +export class SynchronizeOfflineModificationsOnFolderCreated + implements WebdavDomainEventSubscriber +{ + constructor( + private readonly synchronizeOfflineModifications: SynchronizeOfflineModifications + ) {} + + subscribedTo(): DomainEventClass[] { + return [FolderCreatedDomainEvent]; + } + on(domainEvent: FolderCreatedDomainEvent): Promise { + return this.synchronizeOfflineModifications.run(domainEvent.aggregateId); + } +} diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index d5cc1e6ed..215fe419e 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -3,6 +3,7 @@ import { AggregateRoot } from '../../shared/domain/AggregateRoot'; import { FolderPath } from './FolderPath'; import { FolderStatus, FolderStatuses } from './FolderStatus'; import { FolderUuid } from './FolderUuid'; +import { FolderCreatedDomainEvent } from './events/FolderCreatedDomainEvent'; export type FolderAttributes = { id: number; @@ -96,8 +97,8 @@ export class Folder extends AggregateRoot { ); } - static create(attributes: FolderAttributes) { - return new Folder( + static create(attributes: FolderAttributes): Folder { + const folder = new Folder( attributes.id, new FolderUuid(attributes.uuid), new FolderPath(attributes.path), @@ -106,6 +107,13 @@ export class Folder extends AggregateRoot { new Date(attributes.createdAt), FolderStatus.Exists ); + + const folderCreatedEvent = new FolderCreatedDomainEvent({ + aggregateId: attributes.uuid, + }); + folder.record(folderCreatedEvent); + + return folder; } moveTo(folder: Folder) { diff --git a/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts b/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts new file mode 100644 index 000000000..7eda549c9 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts @@ -0,0 +1,18 @@ +import { DomainEvent } from 'workers/sync-engine/modules/shared/domain/DomainEvent'; + +export class FolderCreatedDomainEvent extends DomainEvent { + static readonly EVENT_NAME = 'folder.creation.success'; + + constructor({ aggregateId }: { aggregateId: string }) { + super({ + eventName: FolderCreatedDomainEvent.EVENT_NAME, + aggregateId, + }); + } + + toPrimitives() { + return { + uuid: this.aggregateId, + }; + } +} diff --git a/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts b/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts index 95ca90cb3..8e61ad99b 100644 --- a/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts +++ b/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts @@ -25,7 +25,7 @@ export abstract class DomainEvent { const { aggregateId, eventName, eventId } = params; this.aggregateId = aggregateId; this.eventId = eventId || uuid.v4(); - this.eventName = `webdav.${eventName}`; + this.eventName = `${eventName}`; } abstract toPrimitives(): DomainEventAttributes; diff --git a/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts b/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts index 6ce84b9c8..9838c5925 100644 --- a/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts +++ b/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts @@ -13,7 +13,7 @@ export class NodeJsEventBus extends EventEmitter implements EventBus { addSubscribers(subscribers: DomainEventSubscribers): void { subscribers.items.forEach((subscriber) => { subscriber.subscribedTo().forEach((event) => { - this.on(`webdav.${event.EVENT_NAME}`, subscriber.on.bind(subscriber)); + this.on(`${event.EVENT_NAME}`, subscriber.on.bind(subscriber)); }); }); } From 00ae16274e3022d095a7ba540669a1b5f84d65d6 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 3 Oct 2023 12:06:33 +0200 Subject: [PATCH 05/15] chore: rename file --- src/workers/sync-engine/dependency-injection/common/eventBus.ts | 2 +- .../sync-engine/modules/files/application/FileCreator.ts | 2 +- .../sync-engine/modules/folders/application/FolderCreator.ts | 2 +- .../shared/domain/{WebdavServerEventBus.ts => EventBus.ts} | 0 .../sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts | 2 +- .../sync-engine/modules/shared/test/__mock__/EventBusMock.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/workers/sync-engine/modules/shared/domain/{WebdavServerEventBus.ts => EventBus.ts} (100%) diff --git a/src/workers/sync-engine/dependency-injection/common/eventBus.ts b/src/workers/sync-engine/dependency-injection/common/eventBus.ts index e128cb87f..945e52a16 100644 --- a/src/workers/sync-engine/dependency-injection/common/eventBus.ts +++ b/src/workers/sync-engine/dependency-injection/common/eventBus.ts @@ -1,4 +1,4 @@ -import { EventBus } from '../../modules/shared/domain/WebdavServerEventBus'; +import { EventBus } from '../../modules/shared/domain/EventBus'; import { NodeJsEventBus } from '../../modules/shared/infrastructure/NodeJsEventBus'; export class DependencyInjectionEventBus { diff --git a/src/workers/sync-engine/modules/files/application/FileCreator.ts b/src/workers/sync-engine/modules/files/application/FileCreator.ts index 7d0b11d6e..9e116db40 100644 --- a/src/workers/sync-engine/modules/files/application/FileCreator.ts +++ b/src/workers/sync-engine/modules/files/application/FileCreator.ts @@ -3,7 +3,7 @@ import { FilePath } from '../domain/FilePath'; import { File } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; import { FileSize } from '../domain/FileSize'; -import { EventBus } from '../../shared/domain/WebdavServerEventBus'; +import { EventBus } from '../../shared/domain/EventBus'; import { RemoteFileContents } from '../../contents/domain/RemoteFileContents'; import { FileDeleter } from './FileDeleter'; import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index 34d20c6a4..55bd6ae49 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -1,5 +1,5 @@ import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; -import { EventBus } from '../../shared/domain/WebdavServerEventBus'; +import { EventBus } from '../../shared/domain/EventBus'; import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; diff --git a/src/workers/sync-engine/modules/shared/domain/WebdavServerEventBus.ts b/src/workers/sync-engine/modules/shared/domain/EventBus.ts similarity index 100% rename from src/workers/sync-engine/modules/shared/domain/WebdavServerEventBus.ts rename to src/workers/sync-engine/modules/shared/domain/EventBus.ts diff --git a/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts b/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts index 9838c5925..e56cc73b1 100644 --- a/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts +++ b/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts @@ -1,6 +1,6 @@ import EventEmitter from 'events'; import { DomainEvent } from '../domain/DomainEvent'; -import { EventBus } from '../domain/WebdavServerEventBus'; +import { EventBus } from '../domain/EventBus'; import { DomainEventSubscribers } from './DomainEventSubscribers'; export class NodeJsEventBus extends EventEmitter implements EventBus { diff --git a/src/workers/sync-engine/modules/shared/test/__mock__/EventBusMock.ts b/src/workers/sync-engine/modules/shared/test/__mock__/EventBusMock.ts index 89212bb69..d702eafbf 100644 --- a/src/workers/sync-engine/modules/shared/test/__mock__/EventBusMock.ts +++ b/src/workers/sync-engine/modules/shared/test/__mock__/EventBusMock.ts @@ -1,5 +1,5 @@ import { DomainEvent } from '../../domain/DomainEvent'; -import { EventBus } from '../../domain/WebdavServerEventBus'; +import { EventBus } from '../../domain/EventBus'; import { DomainEventSubscribers } from '../../infrastructure/DomainEventSubscribers'; export class EventBusMock implements EventBus { From 641544202882bfcdc0c34a803a5b34edf02b3391 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 3 Oct 2023 12:54:18 +0200 Subject: [PATCH 06/15] test: SynchronizeOfflineModifications --- .../controllers/AddController.ts | 4 +- .../SynchronizeOfflineModifications.ts | 8 +- .../modules/folders/domain/Folder.ts | 2 +- .../modules/folders/domain/OfflineFolder.ts | 7 +- .../domain/events/FolderCreatedDomainEvent.ts | 2 +- .../SynchronizeOfflineModifications.test.ts | 96 +++++++++++++++++++ .../folders/test/domain/FolderMother.ts | 7 +- .../test/domain/OfflineFolderMother.ts | 30 ++++++ 8 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts create mode 100644 src/workers/sync-engine/modules/folders/test/domain/OfflineFolderMother.ts diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts index 7fda63859..4adb74527 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -85,7 +85,9 @@ export class AddController extends CallbackController { ) => { const offlineFolder = this.offlineFolderCreator.run(absolutePath); callback(true, offlineFolder.uuid); - this.foldersQueue.set(offlineFolder, () => {}); + this.foldersQueue.set(offlineFolder, () => { + //no-op + }); }; async execute( diff --git a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts index 1594b739b..82493dc7a 100644 --- a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts +++ b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts @@ -15,13 +15,14 @@ export class SynchronizeOfflineModifications { async run(uuid: Folder['uuid']) { Logger.debug('Synchronize potential offline changes'); const offlineFolder = this.offlineRepository.getByUuid(uuid); - const folder = this.repository.searchByPartial({ uuid }); if (!offlineFolder) { Logger.debug(`There is no offline folder with ${uuid}`); return; } + const folder = this.repository.searchByPartial({ uuid }); + if (!folder) { Logger.debug('There is no folder with ', uuid); throw new FolderNotFoundError(uuid); @@ -35,6 +36,11 @@ export class SynchronizeOfflineModifications { return; } + if (folder.updatedAt > offlineFolder.updatedAt) { + Logger.debug('Online folder is newer than the offline ', folder.name); + return; + } + Logger.debug('Updating the folder with path: ', offlineFolder.path); await this.renamer.run(folder, offlineFolder.path); Logger.debug('Folder updated with the path: ', offlineFolder.path); diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index 215fe419e..68db45252 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -137,7 +137,7 @@ export class Folder extends AggregateRoot { } this._path = this._path.updateName(newPath.name()); - + this.updatedAt = new Date(); //TODO: record rename event } diff --git a/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts b/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts index 978197ca0..8897c012f 100644 --- a/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts +++ b/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts @@ -14,12 +14,6 @@ export type OfflineFolderAttributes = { status: string; }; -// type FunctionKeys = { -// [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; -// }[keyof T]; - -// type Action = [keyof Folder, Parameters]>]; - export class OfflineFolder extends AggregateRoot { private constructor( private _uuid: FolderUuid, @@ -93,6 +87,7 @@ export class OfflineFolder extends AggregateRoot { } this._path = this._path.updateName(destination.name()); + this.updatedAt = new Date(); } isFolder(): this is OfflineFolder { diff --git a/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts b/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts index 7eda549c9..da800fa18 100644 --- a/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts +++ b/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts @@ -1,4 +1,4 @@ -import { DomainEvent } from 'workers/sync-engine/modules/shared/domain/DomainEvent'; +import { DomainEvent } from '../../../shared/domain/DomainEvent'; export class FolderCreatedDomainEvent extends DomainEvent { static readonly EVENT_NAME = 'folder.creation.success'; diff --git a/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts b/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts new file mode 100644 index 000000000..edc4b87f6 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts @@ -0,0 +1,96 @@ +import { IpcRendererSyncEngineMock } from '../../../shared/test/__mock__/IpcRendererSyncEngineMock'; +import { FolderRenamer } from '../../application/FolderRenamer'; +import { SynchronizeOfflineModifications } from '../../application/SynchronizeOfflineModifications'; +import { InMemoryOfflineFolderRepository } from '../../infrastructure/InMemoryOfflineFolderRepository'; +import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; +import { FolderUuid } from '../../domain/FolderUuid'; +import { OfflineFolderMother } from '../domain/OfflineFolderMother'; +import { FolderMother } from '../domain/FolderMother'; + +describe('Synchronize Offline Modifications', () => { + let offlineRepository: InMemoryOfflineFolderRepository; + let repository: FolderRepositoryMock; + let renamer: FolderRenamer; + + let SUT: SynchronizeOfflineModifications; + + beforeEach(() => { + offlineRepository = new InMemoryOfflineFolderRepository(); + repository = new FolderRepositoryMock(); + renamer = new FolderRenamer(repository, new IpcRendererSyncEngineMock()); + + SUT = new SynchronizeOfflineModifications( + offlineRepository, + repository, + renamer + ); + }); + + it('does nothing if there is no offline folder with the given uuid', async () => { + jest.spyOn(offlineRepository, 'getByUuid').mockReturnValueOnce(undefined); + + await SUT.run(FolderUuid.random().value); + + expect(repository.mockSearchByPartial).not.toBeCalled(); + }); + + it('throws an error if there is no folder with the given uuid', async () => { + jest + .spyOn(offlineRepository, 'getByUuid') + .mockReturnValueOnce(OfflineFolderMother.random()); + + repository.mockSearchByPartial.mockReturnValueOnce(undefined); + + try { + await SUT.run(FolderUuid.random().value); + fail('Expected SUT.run to throw an error, but it did not.'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } + }); + + it('does nothing if the name of the online and offline folder is the same', async () => { + const offlineFolder = OfflineFolderMother.random(); + const folder = FolderMother.fromPartial(offlineFolder.attributes()); + + const offlineRepositorySyp = jest + .spyOn(offlineRepository, 'getByUuid') + .mockReturnValueOnce(offlineFolder); + + const renamerSpy = jest.spyOn(renamer, 'run'); + + repository.mockSearchByPartial.mockReturnValueOnce(folder); + + SUT.run(offlineFolder.uuid); + + expect(offlineRepositorySyp).toBeCalledWith(offlineFolder.uuid); + expect(repository.mockSearchByPartial).toBeCalledWith({ + uuid: offlineFolder.uuid, + }); + expect(renamerSpy).not.toBeCalled(); + }); + + it('only renames the online folder if the modification data is older than the offline one', async () => { + const offlineFolder = OfflineFolderMother.fromPartial({ + updatedAt: new Date('2023-10-01').toISOString(), + path: '/new-name', + }); + const folder = FolderMother.fromPartial({ + ...offlineFolder.attributes(), + updatedAt: new Date('2000-10-01').toISOString(), + path: '/old-name', + }); + + jest + .spyOn(offlineRepository, 'getByUuid') + .mockReturnValueOnce(offlineFolder); + + const renamerSpy = jest.spyOn(renamer, 'run'); + + repository.mockSearchByPartial.mockReturnValueOnce(folder); + + SUT.run(offlineFolder.uuid); + + expect(renamerSpy).toBeCalledWith(folder, offlineFolder.path); + }); +}); diff --git a/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts b/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts index 36d203721..023becbd8 100644 --- a/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts +++ b/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts @@ -1,6 +1,6 @@ import { File } from '../../../files/domain/File'; import { FolderStatuses } from '../../domain/FolderStatus'; -import { Folder } from '../../domain/Folder'; +import { Folder, FolderAttributes } from '../../domain/Folder'; import { FolderUuid } from '../../domain/FolderUuid'; import Chance from 'chance'; const chance = new Chance(); @@ -89,4 +89,9 @@ export class FolderMother { status: FolderStatuses.EXISTS, }); } + + static fromPartial(partial: Partial) { + const any = FolderMother.any(); + return Folder.from({ ...any.attributes(), ...partial }); + } } diff --git a/src/workers/sync-engine/modules/folders/test/domain/OfflineFolderMother.ts b/src/workers/sync-engine/modules/folders/test/domain/OfflineFolderMother.ts new file mode 100644 index 000000000..75d25e2f7 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/test/domain/OfflineFolderMother.ts @@ -0,0 +1,30 @@ +import { FolderStatuses } from '../../domain/FolderStatus'; +import { FolderUuid } from '../../domain/FolderUuid'; +import { + OfflineFolder, + OfflineFolderAttributes, +} from '../../domain/OfflineFolder'; + +import Chance from 'chance'; +const chance = new Chance(); + +export class OfflineFolderMother { + static random(): OfflineFolder { + return OfflineFolder.from({ + uuid: FolderUuid.random().value, + parentId: chance.integer({ min: 1 }), + path: `/${chance.name()}`, + updatedAt: new Date().toISOString(), + createdAt: new Date().toISOString(), + status: FolderStatuses.EXISTS, + }); + } + + static fromPartial(partial: Partial): OfflineFolder { + const random = OfflineFolderMother.random(); + return OfflineFolder.from({ + ...random.attributes(), + ...partial, + }); + } +} From 654561ddd7480d1ea4d82ee4c2fd16cf55499ce5 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 3 Oct 2023 15:31:48 +0200 Subject: [PATCH 07/15] feat: create folders with offline uuid --- .../modules/folders/application/FolderCreator.ts | 6 +++++- .../modules/folders/domain/FolderRepository.ts | 3 ++- .../folders/infrastructure/HttpFolderRepository.ts | 7 ++++++- .../folders/test/__mocks__/FolderRepositoryMock.ts | 8 ++++++-- .../test/infrastructure/HttpFolderRepository.test.ts | 3 ++- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index 55bd6ae49..e42ef3293 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -23,7 +23,11 @@ export class FolderCreator { PlatformPathConverter.winToPosix(offlineFolder.dirname) ); - const folder = await this.repository.create(offlineFolder.path, parent.id); + const folder = await this.repository.create( + offlineFolder.path, + parent.id, + offlineFolder.uuid + ); const events = folder.pullDomainEvents(); this.eventBus.publish(events); diff --git a/src/workers/sync-engine/modules/folders/domain/FolderRepository.ts b/src/workers/sync-engine/modules/folders/domain/FolderRepository.ts index 6735c624d..046a2f837 100644 --- a/src/workers/sync-engine/modules/folders/domain/FolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/domain/FolderRepository.ts @@ -9,7 +9,8 @@ export interface FolderRepository { create( name: FolderPath, - parentId: FolderAttributes['parentId'] + parentId: FolderAttributes['parentId'], + uuid: Folder['uuid'] ): Promise; updateName(folder: Folder): Promise; diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index 76265aad0..8a106e98a 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts @@ -75,7 +75,11 @@ export class HttpFolderRepository implements FolderRepository { return undefined; } - async create(path: FolderPath, parentId: number): Promise { + async create( + path: FolderPath, + parentId: number, + uuid: Folder['uuid'] + ): Promise { const plainName = path.name(); if (!plainName) { @@ -87,6 +91,7 @@ export class HttpFolderRepository implements FolderRepository { { folderName: plainName, parentFolderId: parentId, + uuid, } ); diff --git a/src/workers/sync-engine/modules/folders/test/__mocks__/FolderRepositoryMock.ts b/src/workers/sync-engine/modules/folders/test/__mocks__/FolderRepositoryMock.ts index 7e241a78e..89900ffd3 100644 --- a/src/workers/sync-engine/modules/folders/test/__mocks__/FolderRepositoryMock.ts +++ b/src/workers/sync-engine/modules/folders/test/__mocks__/FolderRepositoryMock.ts @@ -33,8 +33,12 @@ export class FolderRepositoryMock implements FolderRepository { return this.mockUpdateParentDir(item); } - create(name: FolderPath, parentId: number | null): Promise { - return this.mockCreate(name, parentId); + create( + name: FolderPath, + parentId: number | null, + uuid: Folder['uuid'] + ): Promise { + return this.mockCreate(name, parentId, uuid); } searchOn(folder: Folder): Promise { diff --git a/src/workers/sync-engine/modules/folders/test/infrastructure/HttpFolderRepository.test.ts b/src/workers/sync-engine/modules/folders/test/infrastructure/HttpFolderRepository.test.ts index 31dc6c10f..f8e24ed94 100644 --- a/src/workers/sync-engine/modules/folders/test/infrastructure/HttpFolderRepository.test.ts +++ b/src/workers/sync-engine/modules/folders/test/infrastructure/HttpFolderRepository.test.ts @@ -36,7 +36,8 @@ describe('Http Folder Repository', () => { await SUT.create( new FolderPath(`/${serverFolder.name}`), - serverFolder.parentId as unknown as number + serverFolder.parentId as unknown as number, + 'd337ce74-4524-5e87-aebd-0a6f56628065' ); const savedFolder = SUT.search(`/${serverFolder.name}`); From 7b06378aaafce50758385a3b83a81527e53cf97b Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 11:16:23 +0200 Subject: [PATCH 08/15] wip --- .../controllers/AddController.ts | 24 ++++++---- .../controllers/CallbackController.ts | 18 ++++---- .../controllers/DeleteController.ts | 11 ++++- .../controllers/RenameOrMoveController.ts | 13 ++++-- .../offline/OfflineRenameOrMoveController.ts | 13 ++++-- .../executeControllerWithFallback.ts | 2 +- .../dependency-injection/folders/builder.ts | 3 +- .../Offline/OfflineFolderCreator.ts | 12 ++++- .../Offline/OfflineFolderRenamer.ts | 3 -- .../SynchronizeOfflineModifications.ts | 46 +++++++++++-------- .../modules/folders/domain/Folder.ts | 11 ++++- .../modules/folders/domain/OfflineFolder.ts | 10 ++++ .../folders/domain/OfflineFolderRepository.ts | 1 + .../domain/events/FolderCreatedDomainEvent.ts | 2 +- .../InMemoryOfflineFolderRepository.ts | 23 ++++++++-- .../modules/shared/domain/DomainEvent.ts | 4 +- 16 files changed, 141 insertions(+), 55 deletions(-) diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts index 4adb74527..cf17943d5 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -6,6 +6,8 @@ import { MapObserver } from '../../modules/shared/domain/MapObserver'; import { FileCreationOrchestrator } from '../../modules/boundaryBridge/application/FileCreationOrchestrator'; import { OfflineFolderCreator } from '../../modules/folders/application/Offline/OfflineFolderCreator'; import { OfflineFolder } from 'workers/sync-engine/modules/folders/domain/OfflineFolder'; +import { createFilePlaceholderId } from 'workers/sync-engine/modules/placeholders/domain/FilePlaceholderId'; +import { createFolderPlaceholderId } from 'workers/sync-engine/modules/placeholders/domain/FolderPlaceholderId'; type FileCreationQueue = Map< string, @@ -43,7 +45,7 @@ export class AddController extends CallbackController { ) => { try { const contentsId = await this.fileCreationOrchestrator.run(absolutePath); - return callback(true, contentsId); + return callback(true, createFilePlaceholderId(contentsId)); } catch (error: unknown) { Logger.error('Error when adding a file: ', error); callback(false, ''); @@ -59,8 +61,9 @@ export class AddController extends CallbackController { }; private createFolders = async () => { - for (const [absolutePath, callback] of this.foldersQueue) { - await this.createFolder(absolutePath, callback); + Logger.debug('FOLDERS TO CREATE', this.foldersQueue.size); + for (const [offlineFolder, callback] of this.foldersQueue) { + await this.createFolder(offlineFolder, callback); } }; @@ -83,11 +86,16 @@ export class AddController extends CallbackController { absolutePath: string, callback: CreationCallback ) => { - const offlineFolder = this.offlineFolderCreator.run(absolutePath); - callback(true, offlineFolder.uuid); - this.foldersQueue.set(offlineFolder, () => { - //no-op - }); + try { + const offlineFolder = this.offlineFolderCreator.run(absolutePath); + callback(true, createFolderPlaceholderId(offlineFolder.uuid)); + this.foldersQueue.set(offlineFolder, () => { + //no-op + }); + } catch (error: unknown) { + Logger.error('Error on folder creation: ', error); + callback(false, ''); + } }; async execute( diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts index ee92b1a5a..4395fec3e 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts @@ -1,5 +1,11 @@ -import { isFilePlaceholderId } from '../../modules/placeholders/domain/FilePlaceholderId'; -import { isFolderPlaceholderId } from '../../modules/placeholders/domain/FolderPlaceholderId'; +import { + FilePlaceholderId, + isFilePlaceholderId, +} from '../../modules/placeholders/domain/FilePlaceholderId'; +import { + FolderPlaceholderId, + isFolderPlaceholderId, +} from '../../modules/placeholders/domain/FolderPlaceholderId'; export abstract class CallbackController { protected trim(id: string): string { @@ -10,23 +16,19 @@ export abstract class CallbackController { ); } - protected isFilePlaceholder(id: string): boolean { + protected isFilePlaceholder(id: string): id is FilePlaceholderId { // make sure the id is trimmed before comparing // if it was already trimmed should not change its length const trimmed = this.trim(id); - return trimmed.length === 24; - return isFilePlaceholderId(trimmed); } - protected isFolderPlaceholder(id: string): boolean { + protected isFolderPlaceholder(id: string): id is FolderPlaceholderId { // make sure the id is trimmed before comparing // if it was already trimmed should not change its length const trimmed = this.trim(id); - return trimmed.length > 24; - return isFolderPlaceholderId(trimmed); } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index 63711d27f..38551689f 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -44,10 +44,17 @@ export class DeleteController extends CallbackController { const trimmedId = this.trim(contentsId); if (this.isFilePlaceholder(trimmedId)) { - this.filesQueue.push(trimmedId); + const [_, contentsId] = trimmedId.split(':'); + this.filesQueue.push(contentsId); return; } - this.foldersQueue.push(trimmedId); + if (this.isFolderPlaceholder(trimmedId)) { + const [_, folderUuid] = trimmedId.split(':'); + this.foldersQueue.push(folderUuid); + return; + } + + throw new Error(`Placeholder Id not identified: ${trimmedId}`); } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts index 108d3cd4a..07f867fe2 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts @@ -31,12 +31,19 @@ export class RenameOrMoveController extends CallbackController { const relative = this.filePathFromAbsolutePathCreator.run(absolutePath); if (this.isFilePlaceholder(trimmedId)) { - await this.filePathUpdater.run(trimmedId, relative); + const [_, contentsId] = trimmedId.split(':'); + await this.filePathUpdater.run(contentsId, relative); return callback(true); } - await this.folderPathUpdater.run(trimmedId, absolutePath); - callback(true); + if (this.isFolderPlaceholder(trimmedId)) { + const [_, folderUuid] = trimmedId.split(':'); + await this.folderPathUpdater.run(folderUuid, absolutePath); + return callback(true); + } + + Logger.error('Unidentified placeholder id: ', trimmedId); + callback(false); } catch (error: unknown) { Logger.error(error); callback(false); diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts index 0c21ee445..2f4f193f4 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts @@ -21,11 +21,18 @@ export class OfflineRenameOrMoveController extends CallbackController { } if (this.isFilePlaceholder(trimmedId)) { + Logger.error('Tried to rename or move an offline file'); return callback(false); } - Logger.debug('absolute path', absolutePath); - await this.folderPathUpdater.run(trimmedId, absolutePath); - callback(true); + + if (this.isFolderPlaceholder(trimmedId)) { + const [_, folderUuid] = trimmedId.split(':'); + await this.folderPathUpdater.run(folderUuid, absolutePath); + return callback(true); + } + + Logger.error('Placeholder id not identified: ', trimmedId); + callback(false); } catch (error: unknown) { Logger.error(error); callback(false); diff --git a/src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts b/src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts index d51d179fe..869ff7382 100644 --- a/src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts +++ b/src/workers/sync-engine/callbacks-controllers/middlewares/executeControllerWithFallback.ts @@ -18,7 +18,7 @@ export const executeControllerWithFallback = handler(absolutePath, placeholderId, (response: boolean) => { if (!response) { Logger.warn('Default handler failed, running fallback'); - fallback(absolutePath, placeholderId, callback); + fallback(absolutePath, placeholderId, callback.bind(fallback)); return; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 565709b7c..2e5eaed63 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -79,7 +79,8 @@ export async function buildFoldersContainer( const offlineFolderCreator = new OfflineFolderCreator( folderPathCreator, folderFinder, - offlineRepository + offlineRepository, + repository ); const offlineFolderMover = new OfflineFolderMover( diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts index d14b3e87c..a6ef667a6 100644 --- a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts @@ -1,4 +1,5 @@ import { PlatformPathConverter } from '../../../shared/test/helpers/PlatformPathConverter'; +import { FolderRepository } from '../../domain/FolderRepository'; import { OfflineFolder } from '../../domain/OfflineFolder'; import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; import { FolderFinder } from '../FolderFinder'; @@ -8,7 +9,8 @@ export class OfflineFolderCreator { constructor( private readonly pathCreator: FolderPathCreator, private readonly folderFinder: FolderFinder, - private readonly offlineRepository: OfflineFolderRepository + private readonly offlineRepository: OfflineFolderRepository, + private readonly repository: FolderRepository ) {} run(absolutePath: string): OfflineFolder { @@ -16,6 +18,14 @@ export class OfflineFolderCreator { PlatformPathConverter.winToPosix(absolutePath) ); + const onlineFolder = this.repository.searchByPartial({ + path: folderPath.value, + }); + + if (onlineFolder) { + throw new Error('The folder already exists on remote'); + } + const parent = this.folderFinder.run( PlatformPathConverter.winToPosix(folderPath.dirname()) ); diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts index b17f8891b..957fe0f3d 100644 --- a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderRenamer.ts @@ -2,13 +2,10 @@ import { FolderPath } from '../../domain/FolderPath'; import { OfflineFolder } from '../../domain/OfflineFolder'; import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; -import Logger from 'electron-log'; - export class OfflineFolderRenamer { constructor(private readonly offlineFiles: OfflineFolderRepository) {} run(folder: OfflineFolder, destination: FolderPath) { - Logger.debug('RENAME TO ', destination); folder.rename(destination); this.offlineFiles.update(folder); } diff --git a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts index 82493dc7a..39f6229c0 100644 --- a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts +++ b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModifications.ts @@ -2,6 +2,7 @@ import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import { OfflineFolderRepository } from '../domain/OfflineFolderRepository'; import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; +import { FolderRenamedDomainEvent } from '../domain/events/FolderRenamedDomainEvent'; import { FolderRenamer } from './FolderRenamer'; import Logger from 'electron-log'; @@ -13,7 +14,8 @@ export class SynchronizeOfflineModifications { ) {} async run(uuid: Folder['uuid']) { - Logger.debug('Synchronize potential offline changes'); + Logger.debug('Synchronize potential offline changes for folder: ', uuid); + const offlineFolder = this.offlineRepository.getByUuid(uuid); if (!offlineFolder) { @@ -21,28 +23,34 @@ export class SynchronizeOfflineModifications { return; } - const folder = this.repository.searchByPartial({ uuid }); + const events = offlineFolder.pullDomainEvents(); - if (!folder) { - Logger.debug('There is no folder with ', uuid); - throw new FolderNotFoundError(uuid); - } + for (const event of events) { + if (event.eventName !== FolderRenamedDomainEvent.EVENT_NAME) { + continue; + } - if (offlineFolder.name === folder.name) { - Logger.debug( - 'Offline and online folder have the same name: ', - folder.name - ); - return; - } + const rename = event as FolderRenamedDomainEvent; - if (folder.updatedAt > offlineFolder.updatedAt) { - Logger.debug('Online folder is newer than the offline ', folder.name); - return; + const folder = this.repository.searchByPartial({ uuid }); + + if (!folder) { + throw new FolderNotFoundError(uuid); + } + + if (rename.previousPath !== folder.path.value) { + continue; + } + + try { + Logger.debug('Updating the folder with path: ', offlineFolder.path); + await this.renamer.run(folder, offlineFolder.path); + Logger.debug('Folder updated with the path: ', offlineFolder.path); + } catch (error: unknown) { + Logger.error(error); + } } - Logger.debug('Updating the folder with path: ', offlineFolder.path); - await this.renamer.run(folder, offlineFolder.path); - Logger.debug('Folder updated with the path: ', offlineFolder.path); + this.offlineRepository.remove(offlineFolder); } } diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index 68db45252..0fb5c1eac 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -4,6 +4,7 @@ import { FolderPath } from './FolderPath'; import { FolderStatus, FolderStatuses } from './FolderStatus'; import { FolderUuid } from './FolderUuid'; import { FolderCreatedDomainEvent } from './events/FolderCreatedDomainEvent'; +import { FolderRenamedDomainEvent } from './events/FolderRenamedDomainEvent'; export type FolderAttributes = { id: number; @@ -132,13 +133,21 @@ export class Folder extends AggregateRoot { } rename(newPath: FolderPath) { + const oldPath = this._path; if (this._path.hasSameName(newPath)) { throw new Error('Cannot rename a folder to the same name'); } this._path = this._path.updateName(newPath.name()); this.updatedAt = new Date(); - //TODO: record rename event + + const event = new FolderRenamedDomainEvent({ + aggregateId: this.uuid, + previousPath: oldPath.name(), + nextPath: this._path.name(), + }); + + this.record(event); } trash() { diff --git a/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts b/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts index 8897c012f..e558fc2e1 100644 --- a/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts +++ b/src/workers/sync-engine/modules/folders/domain/OfflineFolder.ts @@ -4,6 +4,7 @@ import { FolderPath } from './FolderPath'; import { FolderStatus, FolderStatuses } from './FolderStatus'; import { FolderUuid } from './FolderUuid'; import { Folder } from './Folder'; +import { FolderRenamedDomainEvent } from './events/FolderRenamedDomainEvent'; export type OfflineFolderAttributes = { uuid: string; @@ -82,12 +83,21 @@ export class OfflineFolder extends AggregateRoot { } rename(destination: FolderPath) { + const oldPath = this._path; if (this._path.hasSameName(destination)) { throw new Error('Cannot rename a folder to the same name'); } this._path = this._path.updateName(destination.name()); this.updatedAt = new Date(); + + const event = new FolderRenamedDomainEvent({ + aggregateId: this.uuid, + previousPath: oldPath.value, + nextPath: this._path.value, + }); + + this.record(event); } isFolder(): this is OfflineFolder { diff --git a/src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts b/src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts index 24ff0a0e7..167d777bf 100644 --- a/src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/domain/OfflineFolderRepository.ts @@ -4,4 +4,5 @@ export interface OfflineFolderRepository { getByUuid(uuid: OfflineFolder['uuid']): OfflineFolder | undefined; getByPath(path: string): OfflineFolder | undefined; update(folder: OfflineFolder): void; + remove(folder: OfflineFolder): void; } diff --git a/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts b/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts index da800fa18..3a534b03c 100644 --- a/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts +++ b/src/workers/sync-engine/modules/folders/domain/events/FolderCreatedDomainEvent.ts @@ -1,7 +1,7 @@ import { DomainEvent } from '../../../shared/domain/DomainEvent'; export class FolderCreatedDomainEvent extends DomainEvent { - static readonly EVENT_NAME = 'folder.creation.success'; + static readonly EVENT_NAME = 'folder.created'; constructor({ aggregateId }: { aggregateId: string }) { super({ diff --git a/src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts index 8aa74556e..b9801e396 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/InMemoryOfflineFolderRepository.ts @@ -17,8 +17,25 @@ export class InMemoryOfflineFolderRepository } update(folder: OfflineFolder): void { - Logger.debug('OFFLINE FOLDER UPDATED:', folder.attributes()); - this.foldersByPath[folder.path.value] = folder; - this.foldersByUuid[folder.uuid] = folder; + try { + const storedFolder = this.foldersByPath[folder.path.value] as + | OfflineFolder + | undefined; + + const storedEvents = storedFolder ? storedFolder.pullDomainEvents() : []; + const newEvents = folder.pullDomainEvents(); + + [...storedEvents, ...newEvents].forEach((event) => folder.record(event)); + + this.foldersByPath[folder.path.value] = folder; + this.foldersByUuid[folder.uuid] = folder; + } catch (error: unknown) { + Logger.error('ERROR UPDATING OFFLINE FOLDER', error); + } + } + + remove(folder: OfflineFolder): void { + delete this.foldersByPath[folder.path.value]; + delete this.foldersByUuid[folder.uuid]; } } diff --git a/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts b/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts index 8e61ad99b..d23a98ba1 100644 --- a/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts +++ b/src/workers/sync-engine/modules/shared/domain/DomainEvent.ts @@ -15,6 +15,7 @@ export abstract class DomainEvent { readonly aggregateId: string; readonly eventId: string; readonly eventName: string; + readonly occurredOn?: Date; constructor(params: { eventName: string; @@ -22,10 +23,11 @@ export abstract class DomainEvent { eventId?: string; occurredOn?: Date; }) { - const { aggregateId, eventName, eventId } = params; + const { aggregateId, eventName, eventId, occurredOn } = params; this.aggregateId = aggregateId; this.eventId = eventId || uuid.v4(); this.eventName = `${eventName}`; + this.occurredOn = occurredOn || new Date(); } abstract toPrimitives(): DomainEventAttributes; From 28a3a417e11db9d3d1decdf1c011b9e90ce61d33 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 11:16:32 +0200 Subject: [PATCH 09/15] wip --- .../domain/events/FolderRenamedDomainEvent.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/workers/sync-engine/modules/folders/domain/events/FolderRenamedDomainEvent.ts diff --git a/src/workers/sync-engine/modules/folders/domain/events/FolderRenamedDomainEvent.ts b/src/workers/sync-engine/modules/folders/domain/events/FolderRenamedDomainEvent.ts new file mode 100644 index 000000000..deafd9f48 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/domain/events/FolderRenamedDomainEvent.ts @@ -0,0 +1,34 @@ +import { DomainEvent } from '../../../shared/domain/DomainEvent'; + +export class FolderRenamedDomainEvent extends DomainEvent { + static readonly EVENT_NAME = 'folder.renamed'; + + previousPath: string; + nextPath: string; + + constructor({ + aggregateId, + previousPath: previousName, + nextPath: nextName, + }: { + aggregateId: string; + previousPath: string; + nextPath: string; + }) { + super({ + eventName: FolderRenamedDomainEvent.EVENT_NAME, + aggregateId, + }); + + this.previousPath = previousName; + this.nextPath = nextName; + } + + toPrimitives() { + return { + aggregateId: this.aggregateId, + previousPath: this.previousPath, + nextPath: this.nextPath, + }; + } +} From 70c9cf63a432c93809ed2abb8b5926dc640aeb60 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 12:08:39 +0200 Subject: [PATCH 10/15] fix: working (?) --- .../controllers/AddController.ts | 3 ++- .../folders/application/FolderPathCreator.ts | 3 ++- .../folders/application/FolderPathUpdater.ts | 17 ++++++++++++++--- .../modules/folders/domain/Folder.ts | 2 +- .../modules/folders/domain/FolderPath.ts | 7 +++++-- .../infrastructure/HttpFolderRepository.ts | 3 ++- 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts index cf17943d5..a1e95297a 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -105,6 +105,7 @@ export class AddController extends CallbackController { if (rawPathIsFolder(absolutePath)) { this.enqueueFolder(absolutePath, callback); await this.createFolders(); + await this.createFiles(); return; } @@ -112,7 +113,7 @@ export class AddController extends CallbackController { if (this.foldersQueue.size === 0) { Logger.debug('File is not going to be queued. Creating...', absolutePath); - this.createFiles(); + await this.createFiles(); } else { Logger.debug('File has been queued: ', absolutePath); } diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts index fca948223..e59ef96bf 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts @@ -18,6 +18,7 @@ export class FolderPathCreator { const relative = this.calculateRelativePath(this.baseFolder, sanitized); const withSlash = path.sep + relative; - return new FolderPath(withSlash); + const normalized = path.normalize(withSlash); + return new FolderPath(normalized); } } diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts index a55d71986..9949087ee 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts @@ -1,4 +1,3 @@ -import path from 'path'; import { Folder } from '../domain/Folder'; import { FolderPathCreator } from './FolderPathCreator'; import { FolderRepository } from '../domain/FolderRepository'; @@ -6,6 +5,10 @@ import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; import { FolderMover } from './FolderMover'; import { FolderRenamer } from './FolderRenamer'; +import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; +import { FolderPath } from '../domain/FolderPath'; +import path from 'path'; +import Logger from 'electron-log'; export class FolderPathUpdater { constructor( @@ -16,7 +19,7 @@ export class FolderPathUpdater { ) {} async run(uuid: Folder['uuid'], absolutePath: string) { - const normalized = path.normalize(absolutePath); + // const normalized = path.normalize(absolutePath); const folder = this.repository.searchByPartial({ uuid }); @@ -24,7 +27,15 @@ export class FolderPathUpdater { throw new FolderNotFoundError(uuid); } - const desiredPath = this.pathCreator.fromAbsolute(normalized); + const aPath = this.pathCreator.fromAbsolute( + PlatformPathConverter.winToPosix(absolutePath) + ); + + const desiredPath = new FolderPath( + PlatformPathConverter.winToPosix(path.normalize(aPath.value)) + ); + + Logger.debug('desired path', desiredPath); const dirnameChanged = folder.dirname !== desiredPath.dirname(); const nameChanged = folder.name !== desiredPath.name(); diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index 0fb5c1eac..3052fbe1d 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -5,6 +5,7 @@ import { FolderStatus, FolderStatuses } from './FolderStatus'; import { FolderUuid } from './FolderUuid'; import { FolderCreatedDomainEvent } from './events/FolderCreatedDomainEvent'; import { FolderRenamedDomainEvent } from './events/FolderRenamedDomainEvent'; +import Logger from 'electron-log'; export type FolderAttributes = { id: number; @@ -137,7 +138,6 @@ export class Folder extends AggregateRoot { if (this._path.hasSameName(newPath)) { throw new Error('Cannot rename a folder to the same name'); } - this._path = this._path.updateName(newPath.name()); this.updatedAt = new Date(); diff --git a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts index aeecb2c7f..3f8792b6d 100644 --- a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts +++ b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts @@ -1,5 +1,6 @@ import path from 'path'; import { Path } from '../../shared/domain/Path'; +import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; export class FolderPath extends Path { constructor(value: string) { @@ -7,9 +8,11 @@ export class FolderPath extends Path { } static fromParts(parts: Array) { - const full = path.join(...parts); + const full = path.posix.join(...parts); - return new FolderPath(full); + return new FolderPath( + PlatformPathConverter.winToPosix(path.normalize(full)) + ); } name(): string { diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index 8a106e98a..abef641c0 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts @@ -56,7 +56,7 @@ export class HttpFolderRepository implements FolderRepository { } search(path: string): Nullable { - // Logger.debug(Object.keys(this.folders)); + Logger.debug(Object.keys(this.folders)); return this.folders[path]; } @@ -144,6 +144,7 @@ export class HttpFolderRepository implements FolderRepository { delete this.folders[old?.path.value]; } + Logger.debug('PATH BEFORE INDEX', folder.path.value); this.folders[folder.path.value] = folder; } From 7f227f9f9457fab1d261ce21f9fa316f291dfba2 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 13:07:46 +0200 Subject: [PATCH 11/15] fix: use posix paths in the use cases --- .../callbacks-controllers/buildControllers.ts | 3 +- .../controllers/AddController.ts | 42 ++++++++++++------- .../controllers/DownloadFileController.ts | 13 ++++-- .../controllers/RenameOrMoveController.ts | 15 ++++--- .../DependencyContainer.ts | 2 + .../DependencyContainerFactory.ts | 5 ++- .../boundaryBridge/build.ts | 1 - .../dependency-injection/contents/builder.ts | 8 +++- .../files/FilesContainer.ts | 2 - .../dependency-injection/files/builder.ts | 9 +--- .../folders/FoldersContainer.ts | 2 - .../dependency-injection/folders/builder.ts | 8 ---- .../shared/SharedContainer.ts | 7 ++++ .../dependency-injection/shared/builder.ts | 17 ++++++++ .../application/FileCreationOrchestrator.ts | 9 ++-- .../contents/application/ContentsUploader.ts | 13 +++++- .../application/RetryContentsUploader.ts | 4 +- .../modules/files/application/FileCreator.ts | 2 +- .../FilePathFromAbsolutePathCreator.ts | 31 -------------- .../files/application/FilePathUpdater.ts | 3 +- .../test/application/FilePathUpdater.test.ts | 2 +- .../files/test/domain/FilePath.test.ts | 2 +- .../folders/application/FolderCreator.ts | 2 +- .../folders/application/FolderPathUpdater.ts | 16 +------ .../Offline/OfflineFolderCreator.ts | 11 ++--- .../Offline/OfflineFolderPathUpdater.ts | 11 ++--- .../modules/folders/domain/Folder.ts | 1 - .../modules/folders/domain/FolderPath.ts | 2 +- .../infrastructure/HttpFolderRepository.ts | 6 +-- .../test/application/FolderCreator.test.ts | 13 ++---- .../application/FolderPathCreator.test.ts | 19 --------- .../folders/test/domain/FolderPath.test.ts | 2 +- .../AbsolutePathToRelativeConverter.ts} | 14 +++---- .../PlatformPathConverter.ts | 4 ++ .../RelativePathToAbsoluteConverter.ts | 9 ++++ .../AbsolutePathToRelativeConverter.test.ts | 15 +++++++ .../application/PlatformPathConverter.test.ts | 18 ++++++++ 37 files changed, 178 insertions(+), 165 deletions(-) create mode 100644 src/workers/sync-engine/dependency-injection/shared/SharedContainer.ts create mode 100644 src/workers/sync-engine/dependency-injection/shared/builder.ts delete mode 100644 src/workers/sync-engine/modules/files/application/FilePathFromAbsolutePathCreator.ts delete mode 100644 src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts rename src/workers/sync-engine/modules/{folders/application/FolderPathCreator.ts => shared/application/AbsolutePathToRelativeConverter.ts} (54%) rename src/workers/sync-engine/modules/shared/{test/helpers => application}/PlatformPathConverter.ts (81%) create mode 100644 src/workers/sync-engine/modules/shared/application/RelativePathToAbsoluteConverter.ts create mode 100644 src/workers/sync-engine/modules/shared/test/application/AbsolutePathToRelativeConverter.test.ts create mode 100644 src/workers/sync-engine/modules/shared/test/application/PlatformPathConverter.test.ts diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index dfc05a0dd..ecef9b23a 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -7,6 +7,7 @@ import { OfflineRenameOrMoveController } from './controllers/offline/OfflineRena export function buildControllers(container: DependencyContainer) { const addFileController = new AddController( + container.absolutePathToRelativeConverter, container.fileCreationOrchestrator, container.folderCreator, container.offline.folderCreator @@ -18,7 +19,7 @@ export function buildControllers(container: DependencyContainer) { ); const renameOrMoveController = new RenameOrMoveController( - container.filePathFromAbsolutePathCreator, + container.absolutePathToRelativeConverter, container.filePathUpdater, container.folderPathUpdater, deleteController diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts index a1e95297a..0eba55879 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -5,9 +5,11 @@ import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { MapObserver } from '../../modules/shared/domain/MapObserver'; import { FileCreationOrchestrator } from '../../modules/boundaryBridge/application/FileCreationOrchestrator'; import { OfflineFolderCreator } from '../../modules/folders/application/Offline/OfflineFolderCreator'; -import { OfflineFolder } from 'workers/sync-engine/modules/folders/domain/OfflineFolder'; -import { createFilePlaceholderId } from 'workers/sync-engine/modules/placeholders/domain/FilePlaceholderId'; -import { createFolderPlaceholderId } from 'workers/sync-engine/modules/placeholders/domain/FolderPlaceholderId'; +import { OfflineFolder } from '../../modules/folders/domain/OfflineFolder'; +import { createFilePlaceholderId } from '../../modules/placeholders/domain/FilePlaceholderId'; +import { createFolderPlaceholderId } from '../../modules/placeholders/domain/FolderPlaceholderId'; +import { PlatformPathConverter } from '../../modules/shared/application/PlatformPathConverter'; +import { AbsolutePathToRelativeConverter } from '../../modules/shared/application/AbsolutePathToRelativeConverter'; type FileCreationQueue = Map< string, @@ -26,6 +28,7 @@ export class AddController extends CallbackController { private readonly observer: MapObserver; constructor( + private readonly absolutePathToRelativeConverter: AbsolutePathToRelativeConverter, private readonly fileCreationOrchestrator: FileCreationOrchestrator, private readonly folderCreator: FolderCreator, private readonly offlineFolderCreator: OfflineFolderCreator @@ -40,23 +43,25 @@ export class AddController extends CallbackController { } private createFile = async ( - absolutePath: string, + posixRelativePath: string, callback: (acknowledge: boolean, id: string) => void ) => { try { - const contentsId = await this.fileCreationOrchestrator.run(absolutePath); + const contentsId = await this.fileCreationOrchestrator.run( + posixRelativePath + ); return callback(true, createFilePlaceholderId(contentsId)); } catch (error: unknown) { Logger.error('Error when adding a file: ', error); callback(false, ''); } finally { - this.filesQueue.delete(absolutePath); + this.filesQueue.delete(posixRelativePath); } }; private createFiles = async () => { - for (const [absolutePath, callback] of this.filesQueue) { - await this.createFile(absolutePath, callback); + for (const [posixRelativePath, callback] of this.filesQueue) { + await this.createFile(posixRelativePath, callback); } }; @@ -83,11 +88,11 @@ export class AddController extends CallbackController { }; private enqueueFolder = ( - absolutePath: string, + posixRelativePath: string, callback: CreationCallback ) => { try { - const offlineFolder = this.offlineFolderCreator.run(absolutePath); + const offlineFolder = this.offlineFolderCreator.run(posixRelativePath); callback(true, createFolderPlaceholderId(offlineFolder.uuid)); this.foldersQueue.set(offlineFolder, () => { //no-op @@ -102,20 +107,29 @@ export class AddController extends CallbackController { absolutePath: string, callback: CreationCallback ): Promise { + const win32RelativePath = + this.absolutePathToRelativeConverter.run(absolutePath); + + const posixRelativePath = + PlatformPathConverter.winToPosix(win32RelativePath); + if (rawPathIsFolder(absolutePath)) { - this.enqueueFolder(absolutePath, callback); + this.enqueueFolder(posixRelativePath, callback); await this.createFolders(); await this.createFiles(); return; } - this.filesQueue.set(absolutePath, callback); + this.filesQueue.set(posixRelativePath, callback); if (this.foldersQueue.size === 0) { - Logger.debug('File is not going to be queued. Creating...', absolutePath); + Logger.debug( + 'File is not going to be queued. Creating...', + posixRelativePath + ); await this.createFiles(); } else { - Logger.debug('File has been queued: ', absolutePath); + Logger.debug('File has been queued: ', posixRelativePath); } } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts index 2b0c515b2..05938c214 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts @@ -1,7 +1,9 @@ +import { FilePlaceholderId } from 'workers/sync-engine/modules/placeholders/domain/FilePlaceholderId'; import { ContentsDownloader } from '../../modules/contents/application/ContentsDownloader'; import { FileFinderByContentsId } from '../../modules/files/application/FileFinderByContentsId'; import { LocalRepositoryRepositoryRefresher } from '../../modules/files/application/LocalRepositoryRepositoryRefresher'; import { CallbackController } from './CallbackController'; +import Logger from 'electron-log'; export class DownloadFileController extends CallbackController { constructor( @@ -18,12 +20,17 @@ export class DownloadFileController extends CallbackController { return await this.downloader.run(file); } - async execute(contentsId: string): Promise { + async execute(contentsId: FilePlaceholderId): Promise { const trimmedId = this.trim(contentsId); try { - return await this.action(trimmedId); - } catch { + const [_, contentsId] = trimmedId.split(':'); + return await this.action(contentsId); + } catch (error: unknown) { + Logger.error( + 'Error downloading a file, going to refresh and retry: ', + error + ); await this.localRepositoryRefresher.run(); return await new Promise((resolve, reject) => { diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts index 07f867fe2..bfd2ce97e 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts @@ -1,13 +1,14 @@ import { FolderPathUpdater } from '../../modules/folders/application/FolderPathUpdater'; -import { FilePathFromAbsolutePathCreator } from '../../modules/files/application/FilePathFromAbsolutePathCreator'; import { FilePathUpdater } from '../../modules/files/application/FilePathUpdater'; import { CallbackController } from './CallbackController'; import { DeleteController } from './DeleteController'; import Logger from 'electron-log'; +import { AbsolutePathToRelativeConverter } from 'workers/sync-engine/modules/shared/application/AbsolutePathToRelativeConverter'; +import { PlatformPathConverter } from 'workers/sync-engine/modules/shared/application/PlatformPathConverter'; export class RenameOrMoveController extends CallbackController { constructor( - private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, + private readonly absolutePathToRelativeConverter: AbsolutePathToRelativeConverter, private readonly filePathUpdater: FilePathUpdater, private readonly folderPathUpdater: FolderPathUpdater, private readonly deleteController: DeleteController @@ -28,17 +29,21 @@ export class RenameOrMoveController extends CallbackController { return callback(true); } - const relative = this.filePathFromAbsolutePathCreator.run(absolutePath); + const win32RelativePath = + this.absolutePathToRelativeConverter.run(absolutePath); + + const posixRelativePath = + PlatformPathConverter.winToPosix(win32RelativePath); if (this.isFilePlaceholder(trimmedId)) { const [_, contentsId] = trimmedId.split(':'); - await this.filePathUpdater.run(contentsId, relative); + await this.filePathUpdater.run(contentsId, posixRelativePath); return callback(true); } if (this.isFolderPlaceholder(trimmedId)) { const [_, folderUuid] = trimmedId.split(':'); - await this.folderPathUpdater.run(folderUuid, absolutePath); + await this.folderPathUpdater.run(folderUuid, posixRelativePath); return callback(true); } diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainer.ts b/src/workers/sync-engine/dependency-injection/DependencyContainer.ts index 1c6b4c209..10b52ea8f 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainer.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainer.ts @@ -6,6 +6,7 @@ import { FoldersContainer } from './folders/FoldersContainer'; import { ItemsContainer } from './items/ItemsContainer'; import { PlaceholderContainer } from './placeholders/PlaceholdersContainer'; import { BoundaryBridgeContainer } from './boundaryBridge/BoundaryBridgeContainer'; +import { SharedContainer } from './shared/SharedContainer'; export interface DependencyContainer extends ItemsContainer, @@ -13,6 +14,7 @@ export interface DependencyContainer FilesContainer, FoldersContainer, PlaceholderContainer, + SharedContainer, BoundaryBridgeContainer { virtualDrive: VirtualDrive; } diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index 3c4be66fe..3c3ac77c5 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -9,6 +9,7 @@ import { buildItemsContainer } from './items/builder'; import { DependencyInjectionVirtualDrive } from './common/virtualDrive'; import { buildPlaceholdersContainer } from './placeholders/builder'; import { buildBoundaryBridgeContainer } from './boundaryBridge/build'; +import { buildSharedContainer } from './shared/builder'; export class DependencyContainerFactory { private static _container: DependencyContainer | undefined; @@ -39,9 +40,10 @@ export class DependencyContainerFactory { const { bus } = DependencyInjectionEventBus; const { virtualDrive } = DependencyInjectionVirtualDrive; + const sharedContainer = buildSharedContainer(); const itemsContainer = buildItemsContainer(); const placeholderContainer = buildPlaceholdersContainer(itemsContainer); - const contentsContainer = await buildContentsContainer(); + const contentsContainer = await buildContentsContainer(sharedContainer); const foldersContainer = await buildFoldersContainer(placeholderContainer); const { container: filesContainer } = await buildFilesContainer( foldersContainer, @@ -58,6 +60,7 @@ export class DependencyContainerFactory { ...filesContainer, ...foldersContainer, ...placeholderContainer, + ...sharedContainer, ...boundaryBridgeContainer, virtualDrive, diff --git a/src/workers/sync-engine/dependency-injection/boundaryBridge/build.ts b/src/workers/sync-engine/dependency-injection/boundaryBridge/build.ts index a029be284..f006e128f 100644 --- a/src/workers/sync-engine/dependency-injection/boundaryBridge/build.ts +++ b/src/workers/sync-engine/dependency-injection/boundaryBridge/build.ts @@ -9,7 +9,6 @@ export function buildBoundaryBridgeContainer( ): BoundaryBridgeContainer { const fileCreationOrchestrator = new FileCreationOrchestrator( contentsContainer.contentsUploader, - filesContainer.filePathFromAbsolutePathCreator, filesContainer.fileCreator ); diff --git a/src/workers/sync-engine/dependency-injection/contents/builder.ts b/src/workers/sync-engine/dependency-injection/contents/builder.ts index ffd129914..6be55638e 100644 --- a/src/workers/sync-engine/dependency-injection/contents/builder.ts +++ b/src/workers/sync-engine/dependency-injection/contents/builder.ts @@ -10,8 +10,11 @@ import { DependencyInjectionUserProvider } from '../common/user'; import { ContentsContainer } from './ContentsContainer'; import { FSLocalFileWriter } from 'workers/sync-engine/modules/contents/infrastructure/FSLocalFileWriter'; import { RetryContentsUploader } from 'workers/sync-engine/modules/contents/application/RetryContentsUploader'; +import { SharedContainer } from '../shared/SharedContainer'; -export async function buildContentsContainer(): Promise { +export async function buildContentsContainer( + sharedContainer: SharedContainer +): Promise { const user = DependencyInjectionUserProvider.get(); const mnemonic = DependencyInjectionMnemonicProvider.get(); @@ -29,7 +32,8 @@ export async function buildContentsContainer(): Promise { const contentsUploader = new ContentsUploader( contentsManagerFactory, contentsProvider, - ipcRendererSyncEngine + ipcRendererSyncEngine, + sharedContainer.relativePathToAbsoluteConverter ); const retryContentsUploader = new RetryContentsUploader(contentsUploader); diff --git a/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts b/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts index b23b53e74..460dffd54 100644 --- a/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts +++ b/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts @@ -3,7 +3,6 @@ import { FileByPartialSearcher } from '../../modules/files/application/FileByPar import { FileCreator } from '../../modules/files/application/FileCreator'; import { FileDeleter } from '../../modules/files/application/FileDeleter'; import { FileFinderByContentsId } from '../../modules/files/application/FileFinderByContentsId'; -import { FilePathFromAbsolutePathCreator } from '../../modules/files/application/FilePathFromAbsolutePathCreator'; import { FilePathUpdater } from '../../modules/files/application/FilePathUpdater'; import { FilePlaceholderCreatorFromContentsId } from '../../modules/files/application/FilePlaceholderCreatorFromContentsId'; import { FileSearcher } from '../../modules/files/application/FileSearcher'; @@ -16,7 +15,6 @@ export interface FilesContainer { fileByPartialSearcher: FileByPartialSearcher; filePathUpdater: FilePathUpdater; fileCreator: FileCreator; - filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator; fileSearcher: FileSearcher; filePlaceholderCreatorFromContentsId: FilePlaceholderCreatorFromContentsId; createFilePlaceholderOnDeletionFailed: CreateFilePlaceholderOnDeletionFailed; diff --git a/src/workers/sync-engine/dependency-injection/files/builder.ts b/src/workers/sync-engine/dependency-injection/files/builder.ts index c6cf8f4cc..026a8f5c7 100644 --- a/src/workers/sync-engine/dependency-injection/files/builder.ts +++ b/src/workers/sync-engine/dependency-injection/files/builder.ts @@ -6,14 +6,12 @@ import { FileByPartialSearcher } from '../../modules/files/application/FileByPar import { FileCreator } from '../../modules/files/application/FileCreator'; import { FileDeleter } from '../../modules/files/application/FileDeleter'; import { FileFinderByContentsId } from '../../modules/files/application/FileFinderByContentsId'; -import { FilePathFromAbsolutePathCreator } from '../../modules/files/application/FilePathFromAbsolutePathCreator'; import { FilePathUpdater } from '../../modules/files/application/FilePathUpdater'; import { FileSearcher } from '../../modules/files/application/FileSearcher'; import { LocalRepositoryRepositoryRefresher } from '../../modules/files/application/LocalRepositoryRepositoryRefresher'; import { HttpFileRepository } from '../../modules/files/infrastructure/HttpFileRepository'; import { DependencyInjectionHttpClientsProvider } from '../common/clients'; import { DependencyInjectionEventBus } from '../common/eventBus'; -import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { DependencyInjectionTraverserProvider } from '../common/traverser'; import { DependencyInjectionUserProvider } from '../common/user'; import { FoldersContainer } from '../folders/FoldersContainer'; @@ -30,7 +28,7 @@ export async function buildFilesContainer( const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); const user = DependencyInjectionUserProvider.get(); - const localRootFolderPath = DependencyInjectionLocalRootFolderPath.get(); + const { bus: eventBus } = DependencyInjectionEventBus; const fileRepository = new HttpFileRepository( @@ -74,10 +72,6 @@ export async function buildFilesContainer( eventBus ); - const filePathFromAbsolutePathCreator = new FilePathFromAbsolutePathCreator( - localRootFolderPath - ); - const fileSearcher = new FileSearcher(fileRepository); const filePlaceholderCreatorFromContentsId = @@ -98,7 +92,6 @@ export async function buildFilesContainer( fileByPartialSearcher, filePathUpdater, fileCreator, - filePathFromAbsolutePathCreator, fileSearcher, filePlaceholderCreatorFromContentsId: filePlaceholderCreatorFromContentsId, createFilePlaceholderOnDeletionFailed: diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index 5dd040e8f..73c703201 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -3,7 +3,6 @@ import { FolderByPartialSearcher } from '../../modules/folders/application/Folde import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { FolderDeleter } from '../../modules/folders/application/FolderDeleter'; import { FolderFinder } from '../../modules/folders/application/FolderFinder'; -import { FolderPathCreator } from '../../modules/folders/application/FolderPathCreator'; import { FolderPathUpdater } from '../../modules/folders/application/FolderPathUpdater'; import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; import { OfflineFolderCreator } from '../../modules/folders/application/Offline/OfflineFolderCreator'; @@ -14,7 +13,6 @@ import { SynchronizeOfflineModificationsOnFolderCreated } from '../../modules/fo export interface FoldersContainer { folderCreator: FolderCreator; folderFinder: FolderFinder; - folderPathFromAbsolutePathCreator: FolderPathCreator; folderSearcher: FolderSearcher; folderDeleter: FolderDeleter; allParentFoldersStatusIsExists: AllParentFoldersStatusIsExists; diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 2e5eaed63..6c87d9320 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -4,7 +4,6 @@ import { FolderCreator } from 'workers/sync-engine/modules/folders/application/F import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; import { FolderFinder } from 'workers/sync-engine/modules/folders/application/FolderFinder'; import { FolderMover } from 'workers/sync-engine/modules/folders/application/FolderMover'; -import { FolderPathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathCreator'; import { FolderPathUpdater } from 'workers/sync-engine/modules/folders/application/FolderPathUpdater'; import { FolderRenamer } from 'workers/sync-engine/modules/folders/application/FolderRenamer'; import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; @@ -17,7 +16,6 @@ import { InMemoryOfflineFolderRepository } from 'workers/sync-engine/modules/fol import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; import { DependencyInjectionHttpClientsProvider } from '../common/clients'; import { DependencyInjectionEventBus } from '../common/eventBus'; -import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { DependencyInjectionTraverserProvider } from '../common/traverser'; import { PlaceholderContainer } from '../placeholders/PlaceholdersContainer'; import { FoldersContainer } from './FoldersContainer'; @@ -29,7 +27,6 @@ export async function buildFoldersContainer( ): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); - const rootFolderPath = DependencyInjectionLocalRootFolderPath.get(); const eventBus = DependencyInjectionEventBus.bus; const repository = new HttpFolderRepository( @@ -40,7 +37,6 @@ export async function buildFoldersContainer( ); await repository.init(); - const folderPathCreator = new FolderPathCreator(rootFolderPath); const folderFinder = new FolderFinder(repository); @@ -70,14 +66,12 @@ export async function buildFoldersContainer( const folderPathUpdater = new FolderPathUpdater( repository, - folderPathCreator, folderMover, folderRenamer ); const offlineRepository = new InMemoryOfflineFolderRepository(); const offlineFolderCreator = new OfflineFolderCreator( - folderPathCreator, folderFinder, offlineRepository, repository @@ -90,7 +84,6 @@ export async function buildFoldersContainer( const offlineFolderRenamer = new OfflineFolderRenamer(offlineRepository); const offlineFolderPathUpdater = new OfflineFolderPathUpdater( offlineRepository, - folderPathCreator, offlineFolderMover, offlineFolderRenamer ); @@ -108,7 +101,6 @@ export async function buildFoldersContainer( return { folderCreator, folderFinder, - folderPathFromAbsolutePathCreator: folderPathCreator, folderSearcher, folderDeleter, allParentFoldersStatusIsExists: allParentFoldersStatusIsExists, diff --git a/src/workers/sync-engine/dependency-injection/shared/SharedContainer.ts b/src/workers/sync-engine/dependency-injection/shared/SharedContainer.ts new file mode 100644 index 000000000..3b74e6851 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/shared/SharedContainer.ts @@ -0,0 +1,7 @@ +import { AbsolutePathToRelativeConverter } from '../../modules/shared/application/AbsolutePathToRelativeConverter'; +import { RelativePathToAbsoluteConverter } from '../../modules/shared/application/RelativePathToAbsoluteConverter'; + +export interface SharedContainer { + absolutePathToRelativeConverter: AbsolutePathToRelativeConverter; + relativePathToAbsoluteConverter: RelativePathToAbsoluteConverter; +} diff --git a/src/workers/sync-engine/dependency-injection/shared/builder.ts b/src/workers/sync-engine/dependency-injection/shared/builder.ts new file mode 100644 index 000000000..4d6ab78a8 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/shared/builder.ts @@ -0,0 +1,17 @@ +import { AbsolutePathToRelativeConverter } from 'workers/sync-engine/modules/shared/application/AbsolutePathToRelativeConverter'; +import { SharedContainer } from './SharedContainer'; +import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; +import { RelativePathToAbsoluteConverter } from 'workers/sync-engine/modules/shared/application/RelativePathToAbsoluteConverter'; + +export function buildSharedContainer(): SharedContainer { + const localRootFolderPath = DependencyInjectionLocalRootFolderPath.get(); + const absolutePathToRelativeConverter = new AbsolutePathToRelativeConverter( + localRootFolderPath + ); + + const relativePathToAbsoluteConverter = new RelativePathToAbsoluteConverter( + localRootFolderPath + ); + + return { absolutePathToRelativeConverter, relativePathToAbsoluteConverter }; +} diff --git a/src/workers/sync-engine/modules/boundaryBridge/application/FileCreationOrchestrator.ts b/src/workers/sync-engine/modules/boundaryBridge/application/FileCreationOrchestrator.ts index 82faae232..06b57e903 100644 --- a/src/workers/sync-engine/modules/boundaryBridge/application/FileCreationOrchestrator.ts +++ b/src/workers/sync-engine/modules/boundaryBridge/application/FileCreationOrchestrator.ts @@ -1,19 +1,18 @@ import { RetryContentsUploader } from '../../contents/application/RetryContentsUploader'; import { FileCreator } from '../../files/application/FileCreator'; -import { FilePathFromAbsolutePathCreator } from '../../files/application/FilePathFromAbsolutePathCreator'; import { File } from '../../files/domain/File'; +import { FilePath } from '../../files/domain/FilePath'; export class FileCreationOrchestrator { constructor( private readonly contentsUploader: RetryContentsUploader, - private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, private readonly fileCreator: FileCreator ) {} - async run(absolutePath: string): Promise { - const path = this.filePathFromAbsolutePathCreator.run(absolutePath); + async run(posixRelativePath: string): Promise { + const path = new FilePath(posixRelativePath); - const fileContents = await this.contentsUploader.run(absolutePath); + const fileContents = await this.contentsUploader.run(posixRelativePath); const createdFile = await this.fileCreator.run(path, fileContents); diff --git a/src/workers/sync-engine/modules/contents/application/ContentsUploader.ts b/src/workers/sync-engine/modules/contents/application/ContentsUploader.ts index 7d5eba8d2..86cf88f26 100644 --- a/src/workers/sync-engine/modules/contents/application/ContentsUploader.ts +++ b/src/workers/sync-engine/modules/contents/application/ContentsUploader.ts @@ -4,12 +4,15 @@ import { ContentsManagersFactory } from '../domain/ContentsManagersFactory'; import { LocalContentsProvider } from '../domain/LocalFileProvider'; import { RemoteFileContents } from '../domain/RemoteFileContents'; import { LocalFileContents } from '../domain/LocalFileContents'; +import { PlatformPathConverter } from '../../shared/application/PlatformPathConverter'; +import { RelativePathToAbsoluteConverter } from '../../shared/application/RelativePathToAbsoluteConverter'; export class ContentsUploader { constructor( private readonly remoteContentsManagersFactory: ContentsManagersFactory, private readonly contentProvider: LocalContentsProvider, - private readonly ipc: SyncEngineIpc + private readonly ipc: SyncEngineIpc, + private readonly relativePathToAbsoluteConverter: RelativePathToAbsoluteConverter ) {} private registerEvents( @@ -56,7 +59,13 @@ export class ContentsUploader { }); } - async run(absolutePath: string): Promise { + async run(posixRelativePath: string): Promise { + const win32RelativePath = + PlatformPathConverter.posixToWin(posixRelativePath); + + const absolutePath = + this.relativePathToAbsoluteConverter.run(win32RelativePath); + const { contents, abortSignal } = await this.contentProvider.provide( absolutePath ); diff --git a/src/workers/sync-engine/modules/contents/application/RetryContentsUploader.ts b/src/workers/sync-engine/modules/contents/application/RetryContentsUploader.ts index f5f8b791d..697b2afc6 100644 --- a/src/workers/sync-engine/modules/contents/application/RetryContentsUploader.ts +++ b/src/workers/sync-engine/modules/contents/application/RetryContentsUploader.ts @@ -41,12 +41,12 @@ export class RetryContentsUploader { ); } - async run(absolutePath: string): Promise { + async run(posixRelativePath: string): Promise { await new Promise((resolve) => { setTimeout(resolve, RetryContentsUploader.INITIAL_DELAY); }); - const upload = () => this.uploader.run(absolutePath); + const upload = () => this.uploader.run(posixRelativePath); return this.retryUpload(upload); } diff --git a/src/workers/sync-engine/modules/files/application/FileCreator.ts b/src/workers/sync-engine/modules/files/application/FileCreator.ts index 9e116db40..27c9ecc86 100644 --- a/src/workers/sync-engine/modules/files/application/FileCreator.ts +++ b/src/workers/sync-engine/modules/files/application/FileCreator.ts @@ -6,7 +6,7 @@ import { FileSize } from '../domain/FileSize'; import { EventBus } from '../../shared/domain/EventBus'; import { RemoteFileContents } from '../../contents/domain/RemoteFileContents'; import { FileDeleter } from './FileDeleter'; -import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; +import { PlatformPathConverter } from '../../shared/application/PlatformPathConverter'; export class FileCreator { constructor( diff --git a/src/workers/sync-engine/modules/files/application/FilePathFromAbsolutePathCreator.ts b/src/workers/sync-engine/modules/files/application/FilePathFromAbsolutePathCreator.ts deleted file mode 100644 index 693a0b116..000000000 --- a/src/workers/sync-engine/modules/files/application/FilePathFromAbsolutePathCreator.ts +++ /dev/null @@ -1,31 +0,0 @@ -import path from 'path'; -import { FilePath } from '../domain/FilePath'; - -export class FilePathFromAbsolutePathCreator { - constructor(private readonly baseFolder: string) {} - - private calculateRelativePath(basePathString: string, filePathString: string): string { - const basePath = path.parse(basePathString); - const filePath = path.parse(filePathString); - - if (!filePath.root || filePath.root.length === 0 || filePath.root === '\\') { - filePath.root = basePath.root; - filePath.dir = path.join(basePath.root, filePath.dir); - } - - const fixedFilePath = path.join(filePath.dir + path.sep + filePath.base); - - const relativePath = path.relative(basePathString, fixedFilePath); - const relativeFolders = path.dirname(relativePath); - - return path.join(relativeFolders, filePath.base); - } - - run(absolutePath: string): FilePath { - const relative = this.calculateRelativePath(this.baseFolder, absolutePath); - - const withSlash = path.sep + relative; - - return new FilePath(withSlash); - } -} diff --git a/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts b/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts index 0cb934efe..2c2b81e64 100644 --- a/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts +++ b/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts @@ -23,7 +23,8 @@ export class FilePathUpdater { // await this.eventBus.publish(file.pullDomainEvents()); } - async run(contentsId: string, destination: FilePath) { + async run(contentsId: string, posixRelativePath: string) { + const destination = new FilePath(posixRelativePath); const file = this.fileFinderByContentsId.run(contentsId); this.ipc.send('FILE_RENAMING', { diff --git a/src/workers/sync-engine/modules/files/test/application/FilePathUpdater.test.ts b/src/workers/sync-engine/modules/files/test/application/FilePathUpdater.test.ts index 92b9c1178..021dc52d8 100644 --- a/src/workers/sync-engine/modules/files/test/application/FilePathUpdater.test.ts +++ b/src/workers/sync-engine/modules/files/test/application/FilePathUpdater.test.ts @@ -37,7 +37,7 @@ describe('File path updater', () => { `${file.dirname}/_${file.nameWithExtension}` ); - await SUT.run(file.contentsId, destination); + await SUT.run(file.contentsId, destination.value); expect(repository.mockUpdateName).toBeCalledWith( expect.objectContaining(file) diff --git a/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts b/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts index c6593cd1a..dbce3cd81 100644 --- a/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts +++ b/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts @@ -1,5 +1,5 @@ import { FilePath } from '../../domain/FilePath'; -import { PlatformPathConverter } from '../../../shared/test/helpers/PlatformPathConverter'; +import { PlatformPathConverter } from '../../../shared/application/PlatformPathConverter'; import path from 'path'; describe('Path', () => { diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index e42ef3293..683da4df0 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -1,6 +1,6 @@ import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; import { EventBus } from '../../shared/domain/EventBus'; -import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; +import { PlatformPathConverter } from '../../shared/application/PlatformPathConverter'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import { OfflineFolder } from '../domain/OfflineFolder'; diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts index 9949087ee..3b6c07ff4 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts @@ -1,39 +1,27 @@ import { Folder } from '../domain/Folder'; -import { FolderPathCreator } from './FolderPathCreator'; import { FolderRepository } from '../domain/FolderRepository'; import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; import { FolderMover } from './FolderMover'; import { FolderRenamer } from './FolderRenamer'; -import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; import { FolderPath } from '../domain/FolderPath'; -import path from 'path'; import Logger from 'electron-log'; export class FolderPathUpdater { constructor( private readonly repository: FolderRepository, - private readonly pathCreator: FolderPathCreator, private readonly folderMover: FolderMover, private readonly folderRenamer: FolderRenamer ) {} - async run(uuid: Folder['uuid'], absolutePath: string) { - // const normalized = path.normalize(absolutePath); - + async run(uuid: Folder['uuid'], posixRelativePath: string) { const folder = this.repository.searchByPartial({ uuid }); if (!folder) { throw new FolderNotFoundError(uuid); } - const aPath = this.pathCreator.fromAbsolute( - PlatformPathConverter.winToPosix(absolutePath) - ); - - const desiredPath = new FolderPath( - PlatformPathConverter.winToPosix(path.normalize(aPath.value)) - ); + const desiredPath = new FolderPath(posixRelativePath); Logger.debug('desired path', desiredPath); diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts index a6ef667a6..cde71f750 100644 --- a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts @@ -1,22 +1,19 @@ -import { PlatformPathConverter } from '../../../shared/test/helpers/PlatformPathConverter'; +import { PlatformPathConverter } from '../../../shared/application/PlatformPathConverter'; +import { FolderPath } from '../../domain/FolderPath'; import { FolderRepository } from '../../domain/FolderRepository'; import { OfflineFolder } from '../../domain/OfflineFolder'; import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; import { FolderFinder } from '../FolderFinder'; -import { FolderPathCreator } from '../FolderPathCreator'; export class OfflineFolderCreator { constructor( - private readonly pathCreator: FolderPathCreator, private readonly folderFinder: FolderFinder, private readonly offlineRepository: OfflineFolderRepository, private readonly repository: FolderRepository ) {} - run(absolutePath: string): OfflineFolder { - const folderPath = this.pathCreator.fromAbsolute( - PlatformPathConverter.winToPosix(absolutePath) - ); + run(posixRelativePath: string): OfflineFolder { + const folderPath = new FolderPath(posixRelativePath); const onlineFolder = this.repository.searchByPartial({ path: folderPath.value, diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts index afe02a508..8e2b24503 100644 --- a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater.ts @@ -1,29 +1,26 @@ -import path from 'path'; import { OfflineFolder } from '../../domain/OfflineFolder'; -import { FolderPathCreator } from '../FolderPathCreator'; import { ActionNotPermittedError } from '../../domain/errors/ActionNotPermittedError'; import { OfflineFolderMover } from './OfflineFolderMover'; import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; import { OfflineFolderRenamer } from './OfflineFolderRenamer'; +import { FolderPath } from '../../domain/FolderPath'; +// TODO: Can be unified with FolderPathUpdater export class OfflineFolderPathUpdater { constructor( private readonly offlineFoldersRepository: OfflineFolderRepository, - private readonly pathCreator: FolderPathCreator, private readonly offlineFolderMover: OfflineFolderMover, private readonly offlineFolderRenamer: OfflineFolderRenamer ) {} - async run(uuid: OfflineFolder['uuid'], absolutePath: string) { - const normalized = path.normalize(absolutePath); - + async run(uuid: OfflineFolder['uuid'], posixRelativePath: string) { const folder = this.offlineFoldersRepository.getByUuid(uuid); if (!folder) { throw new Error(`Folder ${uuid} not found in offline folders`); } - const desiredPath = this.pathCreator.fromAbsolute(normalized); + const desiredPath = new FolderPath(posixRelativePath); const dirnameChanged = folder.dirname !== desiredPath.dirname(); const nameChanged = folder.name !== desiredPath.name(); diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index 3052fbe1d..84cb6e2b5 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -5,7 +5,6 @@ import { FolderStatus, FolderStatuses } from './FolderStatus'; import { FolderUuid } from './FolderUuid'; import { FolderCreatedDomainEvent } from './events/FolderCreatedDomainEvent'; import { FolderRenamedDomainEvent } from './events/FolderRenamedDomainEvent'; -import Logger from 'electron-log'; export type FolderAttributes = { id: number; diff --git a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts index 3f8792b6d..f5d449173 100644 --- a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts +++ b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts @@ -1,6 +1,6 @@ import path from 'path'; import { Path } from '../../shared/domain/Path'; -import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; +import { PlatformPathConverter } from '../../shared/application/PlatformPathConverter'; export class FolderPath extends Path { constructor(value: string) { diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index abef641c0..ae3394604 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts @@ -13,7 +13,7 @@ import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; import { RemoteItemsGenerator } from '../../items/application/RemoteItemsGenerator'; import { FolderStatus, FolderStatuses } from '../domain/FolderStatus'; import nodePath from 'path'; -import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; +import { PlatformPathConverter } from '../../shared/application/PlatformPathConverter'; export class HttpFolderRepository implements FolderRepository { public folders: Record = {}; @@ -174,10 +174,6 @@ export class HttpFolderRepository implements FolderRepository { } async trash(folder: Folder): Promise { - if (folder.status !== FolderStatus.Trashed) { - throw new Error('The status need to be trashed to be deleted'); - } - const result = await this.trashClient.post( `${process.env.NEW_DRIVE_URL}/drive/storage/trash/add`, { diff --git a/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts index 998f911bd..9cd0554d2 100644 --- a/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts @@ -2,15 +2,14 @@ import path from 'path'; import { IpcRendererSyncEngineMock } from '../../../shared/test/__mock__/IpcRendererSyncEngineMock'; import { FolderCreator } from '../../application/FolderCreator'; import { FolderFinder } from '../../application/FolderFinder'; -import { FolderPathCreator } from '../../application/FolderPathCreator'; import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; import { FolderPath } from '../../domain/FolderPath'; import { FolderMother } from '../domain/FolderMother'; +import { EventBusMock } from 'workers/sync-engine/modules/shared/test/__mock__/EventBusMock'; describe('Folder Creator', () => { let SUT: FolderCreator; - let folderPathCreator: FolderPathCreator; let repository: FolderRepositoryMock; let folderFinder: FolderFinder; let syncEngineIpc: IpcRendererSyncEngineMock; @@ -18,17 +17,13 @@ describe('Folder Creator', () => { const BASE_FOLDER_ID = 'D:\\Users\\HalfBloodPrince\\InternxtDrive'; beforeEach(() => { - folderPathCreator = new FolderPathCreator(BASE_FOLDER_ID); repository = new FolderRepositoryMock(); folderFinder = new FolderFinder(repository); syncEngineIpc = new IpcRendererSyncEngineMock(); - SUT = new FolderCreator( - folderPathCreator, - repository, - folderFinder, - syncEngineIpc - ); + const eventBus = new EventBusMock(); + + SUT = new FolderCreator(repository, folderFinder, syncEngineIpc, eventBus); }); it('creates on a folder on the root folder', async () => { diff --git a/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts deleted file mode 100644 index 31612c56d..000000000 --- a/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import path from 'path'; -import { FolderPathCreator } from '../../application/FolderPathCreator'; - -describe('Folder Phat Creator', () => { - describe('Create from absolute', () => { - it('works', () => { - const ab = 'C\\:Users\\JWcer\\InternxtDrive\\\\New folder (4)\\'; - - const sut = new FolderPathCreator('C\\:Users\\JWcer\\InternxtDrive'); - - const result = sut.fromAbsolute(ab); - - // TODO: This behavior need to change. Normalize any path that is returned form bindings - expect(result.value).toBe('\\New folder (4)'); - expect(path.dirname(result.value)).toBe('\\'); - expect(result.dirname()).toBe(path.sep); - }); - }); -}); diff --git a/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts b/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts index d4a595f1d..53018d2bb 100644 --- a/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts +++ b/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts @@ -1,5 +1,5 @@ import { FolderPath } from '../../domain/FolderPath'; -import { PlatformPathConverter } from '../../../shared/test/helpers/PlatformPathConverter'; +import { PlatformPathConverter } from '../../../shared/application/PlatformPathConverter'; import path from 'path'; describe('Path', () => { diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts b/src/workers/sync-engine/modules/shared/application/AbsolutePathToRelativeConverter.ts similarity index 54% rename from src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts rename to src/workers/sync-engine/modules/shared/application/AbsolutePathToRelativeConverter.ts index e59ef96bf..c71fdc5b6 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts +++ b/src/workers/sync-engine/modules/shared/application/AbsolutePathToRelativeConverter.ts @@ -1,7 +1,6 @@ import path from 'path'; -import { FolderPath } from '../domain/FolderPath'; -export class FolderPathCreator { +export class AbsolutePathToRelativeConverter { constructor(private readonly baseFolder: string) {} private calculateRelativePath(basePath: string, folderPath: string): string { @@ -12,13 +11,10 @@ export class FolderPathCreator { return path.join(relativeFolders, fileName); } - fromAbsolute(absolutePath: string): FolderPath { - // TODO: path.normalize can be better fit - const sanitized = absolutePath.replace('\\\\', '\\'); - const relative = this.calculateRelativePath(this.baseFolder, sanitized); + run(absolutePath: string): string { + const normalized = path.normalize(absolutePath); + const relative = this.calculateRelativePath(this.baseFolder, normalized); - const withSlash = path.sep + relative; - const normalized = path.normalize(withSlash); - return new FolderPath(normalized); + return path.win32.sep + relative; } } diff --git a/src/workers/sync-engine/modules/shared/test/helpers/PlatformPathConverter.ts b/src/workers/sync-engine/modules/shared/application/PlatformPathConverter.ts similarity index 81% rename from src/workers/sync-engine/modules/shared/test/helpers/PlatformPathConverter.ts rename to src/workers/sync-engine/modules/shared/application/PlatformPathConverter.ts index e05d0e1d0..634c9273f 100644 --- a/src/workers/sync-engine/modules/shared/test/helpers/PlatformPathConverter.ts +++ b/src/workers/sync-engine/modules/shared/application/PlatformPathConverter.ts @@ -14,4 +14,8 @@ export class PlatformPathConverter { static winToPosix(win: string): string { return win.split(path.win32.sep).join(path.posix.sep); } + + static posixToWin(posix: string): string { + return posix.split(path.posix.sep).join(path.win32.sep); + } } diff --git a/src/workers/sync-engine/modules/shared/application/RelativePathToAbsoluteConverter.ts b/src/workers/sync-engine/modules/shared/application/RelativePathToAbsoluteConverter.ts new file mode 100644 index 000000000..c050a1a5f --- /dev/null +++ b/src/workers/sync-engine/modules/shared/application/RelativePathToAbsoluteConverter.ts @@ -0,0 +1,9 @@ +import path from 'path'; + +export class RelativePathToAbsoluteConverter { + constructor(private readonly baseFolder: string) {} + + run(relativePath: string): string { + return path.join(this.baseFolder, relativePath); + } +} diff --git a/src/workers/sync-engine/modules/shared/test/application/AbsolutePathToRelativeConverter.test.ts b/src/workers/sync-engine/modules/shared/test/application/AbsolutePathToRelativeConverter.test.ts new file mode 100644 index 000000000..483ccdcdf --- /dev/null +++ b/src/workers/sync-engine/modules/shared/test/application/AbsolutePathToRelativeConverter.test.ts @@ -0,0 +1,15 @@ +import { AbsolutePathToRelativeConverter } from '../../application/AbsolutePathToRelativeConverter'; + +describe('AbsolutePathToRelativeConverter', () => { + it('works', () => { + const absolute = 'C\\:Users\\JWcer\\InternxtDrive\\\\New folder (4)\\'; + + const sut = new AbsolutePathToRelativeConverter( + 'C\\:Users\\JWcer\\InternxtDrive' + ); + + const relative = sut.run(absolute); + + expect(relative).toBe('\\New folder (4)'); + }); +}); diff --git a/src/workers/sync-engine/modules/shared/test/application/PlatformPathConverter.test.ts b/src/workers/sync-engine/modules/shared/test/application/PlatformPathConverter.test.ts new file mode 100644 index 000000000..58f947982 --- /dev/null +++ b/src/workers/sync-engine/modules/shared/test/application/PlatformPathConverter.test.ts @@ -0,0 +1,18 @@ +import { PlatformPathConverter } from '../../application/PlatformPathConverter'; + +describe('PlatformPathConverter', () => { + describe('winToPosix', () => { + it('works with a single level', () => { + const win = '\\New Folder (4)'; + const posix = PlatformPathConverter.winToPosix(win); + + expect(posix).toBe('/New Folder (4)'); + }); + it('works with two levels', () => { + const win = '\\New Folder (4)\\Subfolder'; + const posix = PlatformPathConverter.winToPosix(win); + + expect(posix).toBe('/New Folder (4)/Subfolder'); + }); + }); +}); From d62062dcbedab2373e829fec66296c3980cd6de0 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 14:07:27 +0200 Subject: [PATCH 12/15] chore: disable extract pdf page test --- .../main/thumbnails/extract-pdf-page.test.ts | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/test/main/thumbnails/extract-pdf-page.test.ts b/src/test/main/thumbnails/extract-pdf-page.test.ts index 472bc8de1..c2b9f6c1c 100644 --- a/src/test/main/thumbnails/extract-pdf-page.test.ts +++ b/src/test/main/thumbnails/extract-pdf-page.test.ts @@ -1,43 +1,43 @@ -import { extractFirstPageAsReadablePNG } from '../../../main/thumbnails/application/extract-pdf-page'; -import path from 'path'; -import { compareImageStreams, isAnImage } from './helpers'; -import { unlinkSync, writeFileSync } from 'fs'; -import { ReadStreamToBuffer } from '../../../shared/fs/ReadStreamToBuffer'; -import * as uuid from 'uuid'; +// import { extractFirstPageAsReadablePNG } from '../../../main/thumbnails/application/extract-pdf-page'; +// import path from 'path'; +// import { compareImageStreams, isAnImage } from './helpers'; +// import { unlinkSync, writeFileSync } from 'fs'; +// import { ReadStreamToBuffer } from '../../../shared/fs/ReadStreamToBuffer'; +// import * as uuid from 'uuid'; -describe('Extract First PDF page', () => { - const filesToClean: Array = []; +// describe('Extract First PDF page', () => { +// const filesToClean: Array = []; - function useTemporalPNGFile() { - const name = path.join(__dirname, `${uuid.v4()}.png`); - filesToClean.push(name); +// function useTemporalPNGFile() { +// const name = path.join(__dirname, `${uuid.v4()}.png`); +// filesToClean.push(name); - return name; - } +// return name; +// } - afterAll(() => { - filesToClean.forEach(unlinkSync); - }); +// afterAll(() => { +// filesToClean.forEach(unlinkSync); +// }); - it('extracts a png from a pdf', async () => { - const source = path.join(__dirname, 'fixtures', 'source.pdf'); +// it('extracts a png from a pdf', async () => { +// const source = path.join(__dirname, 'fixtures', 'source.pdf'); - const readable = await extractFirstPageAsReadablePNG(source); +// const readable = await extractFirstPageAsReadablePNG(source); - expect(await isAnImage(readable)).toBe(true); - }); +// expect(await isAnImage(readable)).toBe(true); +// }); - it('the extracted png is the expected', async () => { - const expected = path.join(__dirname, 'fixtures', 'result.png'); - const source = path.join(__dirname, 'fixtures', 'source.pdf'); - const resultPath = useTemporalPNGFile(); +// it('the extracted png is the expected', async () => { +// const expected = path.join(__dirname, 'fixtures', 'result.png'); +// const source = path.join(__dirname, 'fixtures', 'source.pdf'); +// const resultPath = useTemporalPNGFile(); - const result = await extractFirstPageAsReadablePNG(source); +// const result = await extractFirstPageAsReadablePNG(source); - writeFileSync(resultPath, await ReadStreamToBuffer.read(result)); +// writeFileSync(resultPath, await ReadStreamToBuffer.read(result)); - const { isEqual } = await compareImageStreams(expected, resultPath); +// const { isEqual } = await compareImageStreams(expected, resultPath); - expect(isEqual).toBe(true); - }); -}); +// expect(isEqual).toBe(true); +// }); +// }); From 399349606a4229ee9448e496d090989e260b19de Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 14:26:09 +0200 Subject: [PATCH 13/15] feat: ensure paths are posix --- .../main/thumbnails/extract-pdf-page.test.ts | 43 --------- .../files/application/FilePathUpdater.ts | 4 +- .../modules/files/domain/FilePath.ts | 14 +-- .../test/application/FileCreator.test.ts | 2 +- .../files/test/domain/FilePath.test.ts | 24 +---- .../files/test/domain/FilePathMother.ts | 4 +- .../folders/application/FolderCreator.ts | 5 +- .../folders/application/FolderFinder.ts | 2 +- .../modules/folders/domain/FolderPath.ts | 7 +- .../test/application/FolderCreator.test.ts | 88 +++++++++++++------ .../folders/test/domain/FolderPath.test.ts | 13 +-- .../sync-engine/modules/shared/domain/Path.ts | 29 +++--- 12 files changed, 92 insertions(+), 143 deletions(-) delete mode 100644 src/test/main/thumbnails/extract-pdf-page.test.ts diff --git a/src/test/main/thumbnails/extract-pdf-page.test.ts b/src/test/main/thumbnails/extract-pdf-page.test.ts deleted file mode 100644 index c2b9f6c1c..000000000 --- a/src/test/main/thumbnails/extract-pdf-page.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -// import { extractFirstPageAsReadablePNG } from '../../../main/thumbnails/application/extract-pdf-page'; -// import path from 'path'; -// import { compareImageStreams, isAnImage } from './helpers'; -// import { unlinkSync, writeFileSync } from 'fs'; -// import { ReadStreamToBuffer } from '../../../shared/fs/ReadStreamToBuffer'; -// import * as uuid from 'uuid'; - -// describe('Extract First PDF page', () => { -// const filesToClean: Array = []; - -// function useTemporalPNGFile() { -// const name = path.join(__dirname, `${uuid.v4()}.png`); -// filesToClean.push(name); - -// return name; -// } - -// afterAll(() => { -// filesToClean.forEach(unlinkSync); -// }); - -// it('extracts a png from a pdf', async () => { -// const source = path.join(__dirname, 'fixtures', 'source.pdf'); - -// const readable = await extractFirstPageAsReadablePNG(source); - -// expect(await isAnImage(readable)).toBe(true); -// }); - -// it('the extracted png is the expected', async () => { -// const expected = path.join(__dirname, 'fixtures', 'result.png'); -// const source = path.join(__dirname, 'fixtures', 'source.pdf'); -// const resultPath = useTemporalPNGFile(); - -// const result = await extractFirstPageAsReadablePNG(source); - -// writeFileSync(resultPath, await ReadStreamToBuffer.read(result)); - -// const { isEqual } = await compareImageStreams(expected, resultPath); - -// expect(isEqual).toBe(true); -// }); -// }); diff --git a/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts b/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts index 2c2b81e64..c5d94cd62 100644 --- a/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts +++ b/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts @@ -37,9 +37,7 @@ export class FilePathUpdater { throw new ActionNotPermitedError('rename and change folder'); } - const destinationFolder = this.folderFinder.run( - destination.posixDirname() - ); + const destinationFolder = this.folderFinder.run(destination.dirname()); file.moveTo(destinationFolder); diff --git a/src/workers/sync-engine/modules/files/domain/FilePath.ts b/src/workers/sync-engine/modules/files/domain/FilePath.ts index 20d67c16d..a85127c89 100644 --- a/src/workers/sync-engine/modules/files/domain/FilePath.ts +++ b/src/workers/sync-engine/modules/files/domain/FilePath.ts @@ -7,14 +7,14 @@ export class FilePath extends Path { } static fromParts(parts: Array) { - const full = path.join(...parts); + const full = path.posix.join(...parts); return new FilePath(full); } extension(): string { - const base = path.basename(this.value); - const { ext } = path.parse(base); + const base = path.posix.basename(this.value); + const { ext } = path.posix.parse(base); return ext.slice(1); } @@ -25,14 +25,14 @@ export class FilePath extends Path { } name(): string { - const base = path.basename(this.value); - const { name } = path.parse(base); + const base = path.posix.basename(this.value); + const { name } = path.posix.parse(base); return name; } nameWithExtension(): string { - const basename = path.basename(this.value); - const { base } = path.parse(basename); + const basename = path.posix.basename(this.value); + const { base } = path.posix.parse(basename); return base; } diff --git a/src/workers/sync-engine/modules/files/test/application/FileCreator.test.ts b/src/workers/sync-engine/modules/files/test/application/FileCreator.test.ts index ab4da98ef..06935c75d 100644 --- a/src/workers/sync-engine/modules/files/test/application/FileCreator.test.ts +++ b/src/workers/sync-engine/modules/files/test/application/FileCreator.test.ts @@ -84,7 +84,7 @@ describe('File Creator', () => { await SUT.run(path, contents); expect(eventBus.publishMock.mock.calls[0][0][0].eventName).toBe( - 'webdav.file.created' + 'file.created' ); expect(eventBus.publishMock.mock.calls[0][0][0].aggregateId).toBe( contents.id diff --git a/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts b/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts index dbce3cd81..25b2117b7 100644 --- a/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts +++ b/src/workers/sync-engine/modules/files/test/domain/FilePath.test.ts @@ -1,29 +1,7 @@ import { FilePath } from '../../domain/FilePath'; -import { PlatformPathConverter } from '../../../shared/application/PlatformPathConverter'; -import path from 'path'; describe('Path', () => { - describe('path instanciation', () => { - it('path from parts creates expected result', () => { - const parts = [path.sep, 'Family']; - - const filePath = FilePath.fromParts(parts); - - expect(filePath.value).toBe( - PlatformPathConverter.convertAnyToCurrent('/Family') - ); - }); - - it('works', () => { - const folderPath = new FilePath('/Family'); - - const basePath = folderPath.dirname(); - - expect(basePath).toBe(path.sep); - }); - }); - - describe('extension handeling', () => { + describe('extension handling', () => { describe('files without extension', () => { it('when a file has no extension hasExtension returns false', () => { const path = new FilePath('/folder/file'); diff --git a/src/workers/sync-engine/modules/files/test/domain/FilePathMother.ts b/src/workers/sync-engine/modules/files/test/domain/FilePathMother.ts index 965f33705..40ff10927 100644 --- a/src/workers/sync-engine/modules/files/test/domain/FilePathMother.ts +++ b/src/workers/sync-engine/modules/files/test/domain/FilePathMother.ts @@ -12,11 +12,11 @@ export class FilePathMother { const name = `${chance.word()}.${chance.word({ length: 3 })}`; if (level === 0) { - return new FilePath(path.join(name)); + return new FilePath(path.posix.join(name)); } const folders = chance.sentence({ words: level }); - return new FilePath(path.join(...folders, name)); + return new FilePath(path.posix.join(...folders, name)); } } diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index 683da4df0..a4b21e8ea 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -1,6 +1,5 @@ import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; import { EventBus } from '../../shared/domain/EventBus'; -import { PlatformPathConverter } from '../../shared/application/PlatformPathConverter'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import { OfflineFolder } from '../domain/OfflineFolder'; @@ -19,9 +18,7 @@ export class FolderCreator { name: offlineFolder.name, }); - const parent = this.folderFinder.run( - PlatformPathConverter.winToPosix(offlineFolder.dirname) - ); + const parent = this.folderFinder.run(offlineFolder.dirname); const folder = await this.repository.create( offlineFolder.path, diff --git a/src/workers/sync-engine/modules/folders/application/FolderFinder.ts b/src/workers/sync-engine/modules/folders/application/FolderFinder.ts index b59ec3fae..8697af02b 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderFinder.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderFinder.ts @@ -17,7 +17,7 @@ export class FolderFinder { } findFromFilePath(path: FilePath): Folder { - const folder = this.repository.search(path.posixDirname()); + const folder = this.repository.search(path.value); if (!folder) { throw new FolderNotFoundError(path.value); diff --git a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts index f5d449173..1548fcf3b 100644 --- a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts +++ b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts @@ -1,6 +1,5 @@ import path from 'path'; import { Path } from '../../shared/domain/Path'; -import { PlatformPathConverter } from '../../shared/application/PlatformPathConverter'; export class FolderPath extends Path { constructor(value: string) { @@ -10,13 +9,11 @@ export class FolderPath extends Path { static fromParts(parts: Array) { const full = path.posix.join(...parts); - return new FolderPath( - PlatformPathConverter.winToPosix(path.normalize(full)) - ); + return new FolderPath(path.posix.normalize(full)); } name(): string { - if (this.value === path.sep) { + if (this.value === path.posix.sep) { return 'Internxt Drive'; } diff --git a/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts index 9cd0554d2..37ab9778e 100644 --- a/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderCreator.test.ts @@ -1,11 +1,11 @@ -import path from 'path'; import { IpcRendererSyncEngineMock } from '../../../shared/test/__mock__/IpcRendererSyncEngineMock'; import { FolderCreator } from '../../application/FolderCreator'; import { FolderFinder } from '../../application/FolderFinder'; import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; -import { FolderPath } from '../../domain/FolderPath'; import { FolderMother } from '../domain/FolderMother'; -import { EventBusMock } from 'workers/sync-engine/modules/shared/test/__mock__/EventBusMock'; +import { EventBusMock } from '../../../shared/test/__mock__/EventBusMock'; +import { OfflineFolderMother } from '../domain/OfflineFolderMother'; +import { Folder } from '../../domain/Folder'; describe('Folder Creator', () => { let SUT: FolderCreator; @@ -13,62 +13,96 @@ describe('Folder Creator', () => { let repository: FolderRepositoryMock; let folderFinder: FolderFinder; let syncEngineIpc: IpcRendererSyncEngineMock; - - const BASE_FOLDER_ID = 'D:\\Users\\HalfBloodPrince\\InternxtDrive'; + let eventBus: EventBusMock; beforeEach(() => { repository = new FolderRepositoryMock(); folderFinder = new FolderFinder(repository); syncEngineIpc = new IpcRendererSyncEngineMock(); - const eventBus = new EventBusMock(); + eventBus = new EventBusMock(); SUT = new FolderCreator(repository, folderFinder, syncEngineIpc, eventBus); }); - it('creates on a folder on the root folder', async () => { - const folderPath = path.join(BASE_FOLDER_ID, 'lily'); - const expectedPath = new FolderPath('\\lily'); + it('creates on a folder from a offline folder', async () => { + const offlineFolder = OfflineFolderMother.random(); + + const parentFolder = FolderMother.fromPartial({ + id: offlineFolder.parentId, + path: offlineFolder.dirname, + }); + + const resultFolderAttributes = FolderMother.fromPartial( + offlineFolder.attributes() + ).attributes(); - const folder = FolderMother.root(); + repository.mockCreate.mockResolvedValueOnce( + Folder.create(resultFolderAttributes) + ); - const spy = jest.spyOn(folderFinder, 'run').mockReturnValueOnce(folder); + const spy = jest + .spyOn(folderFinder, 'run') + .mockReturnValueOnce(parentFolder); - await SUT.run(folderPath); + await SUT.run(offlineFolder); - expect(spy).toBeCalledWith('/'); - expect(repository.mockCreate).toBeCalledWith(expectedPath, folder.id); + expect(spy).toBeCalledWith(parentFolder.path.value); + expect(repository.mockCreate).toBeCalledWith( + offlineFolder.path, + parentFolder.id, + offlineFolder.uuid + ); }); describe('Synchronization messages', () => { it('sends the message FOLDER_CREATING', async () => { - const folderPath = path.join(BASE_FOLDER_ID, 'lily'); + const offlineFolder = OfflineFolderMother.random(); - const folder = FolderMother.root(); + const parentFolder = FolderMother.fromPartial({ + id: offlineFolder.parentId, + path: offlineFolder.dirname, + }); + + const resultFolderAttributes = FolderMother.fromPartial( + offlineFolder.attributes() + ).attributes(); - jest.spyOn(folderFinder, 'run').mockReturnValueOnce(folder); + repository.mockCreate.mockResolvedValueOnce( + Folder.create(resultFolderAttributes) + ); - await SUT.run(folderPath); + jest.spyOn(folderFinder, 'run').mockReturnValueOnce(parentFolder); + + await SUT.run(offlineFolder); expect(syncEngineIpc.sendMock).toBeCalledWith('FOLDER_CREATING', { - name: 'lily', + name: offlineFolder.name, }); - expect(syncEngineIpc.sendMock).toHaveBeenCalledBefore( - repository.mockCreate - ); }); it('sends the message FOLDER_CREATED', async () => { - const folderPath = path.join(BASE_FOLDER_ID, 'lily'); + const offlineFolder = OfflineFolderMother.random(); + + const parentFolder = FolderMother.fromPartial({ + id: offlineFolder.parentId, + path: offlineFolder.dirname, + }); + + const resultFolderAttributes = FolderMother.fromPartial( + offlineFolder.attributes() + ).attributes(); - const folder = FolderMother.root(); + repository.mockCreate.mockResolvedValueOnce( + Folder.create(resultFolderAttributes) + ); - jest.spyOn(folderFinder, 'run').mockReturnValueOnce(folder); + jest.spyOn(folderFinder, 'run').mockReturnValueOnce(parentFolder); - await SUT.run(folderPath); + await SUT.run(offlineFolder); expect(syncEngineIpc.sendMock).toBeCalledWith('FOLDER_CREATED', { - name: 'lily', + name: offlineFolder.name, }); }); }); diff --git a/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts b/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts index 53018d2bb..faeba3a8e 100644 --- a/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts +++ b/src/workers/sync-engine/modules/folders/test/domain/FolderPath.test.ts @@ -1,25 +1,14 @@ import { FolderPath } from '../../domain/FolderPath'; -import { PlatformPathConverter } from '../../../shared/application/PlatformPathConverter'; import path from 'path'; describe('Path', () => { describe('path instantiation', () => { - it('path from parts creates expected result', () => { - const parts = [path.sep, 'Family']; - - const folderPath = FolderPath.fromParts(parts); - - expect(folderPath.value).toBe( - PlatformPathConverter.convertAnyToCurrent('/Family') - ); - }); - it('extracts the parent folder path', () => { const folderPath = new FolderPath('/Family'); const basePath = folderPath.dirname(); - expect(basePath).toBe(path.sep); + expect(basePath).toBe(path.posix.sep); }); }); }); diff --git a/src/workers/sync-engine/modules/shared/domain/Path.ts b/src/workers/sync-engine/modules/shared/domain/Path.ts index 5ec02d867..a8fa22067 100644 --- a/src/workers/sync-engine/modules/shared/domain/Path.ts +++ b/src/workers/sync-engine/modules/shared/domain/Path.ts @@ -1,38 +1,37 @@ import path from 'path'; import { ValueObject } from '../../../../shared/domain/ValueObject'; +import { InvalidArgumentError } from '../../../../shared/domain/InvalidArgumentError'; export abstract class Path extends ValueObject { - private convertPathToCurrentPlatform(p: string) { - const fromPlatform = p.includes(path.posix.sep) ? path.posix : path.win32; + constructor(value: string) { + super(value); - const toPlatform = path.sep === path.posix.sep ? path.posix : path.win32; - - return p.split(fromPlatform.sep).join(toPlatform.sep); + this.ensurePathIsPosix(value); } - private convertPathToPosix(p: string) { - return p.split(path.win32.sep).join(path.posix.sep); + private ensurePathIsPosix(value: string) { + const isPosix = value.indexOf('/') !== -1; + + if (!isPosix) { + throw new InvalidArgumentError(`Paths have to be posix, path: ${value}`); + } } name(): string { - const base = path.basename(this.value); - const { name } = path.parse(base); + const base = path.posix.basename(this.value); + const { name } = path.posix.parse(base); return name; } dirname(): string { - const dirname = this.convertPathToCurrentPlatform(path.dirname(this.value)); + const dirname = path.posix.dirname(this.value); if (dirname === '.') { - return path.sep; + return path.posix.sep; } return dirname; } - posixDirname(): string { - return this.convertPathToPosix(path.dirname(this.value)); - } - hasSameName(other: Path) { const name = this.name(); const otherName = other.name(); From 00780a61c4c9413028770f54b0eaed07a71c250e Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 15:36:17 +0200 Subject: [PATCH 14/15] feat: synchronize mutliple renames --- ...nizeOfflineModificationsOnFolderCreated.ts | 4 +- .../modules/folders/domain/FolderPath.ts | 6 +- .../SynchronizeOfflineModifications.test.ts | 69 ++++++++++++++----- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts index d60a3d2a6..86a2bed45 100644 --- a/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts +++ b/src/workers/sync-engine/modules/folders/application/SynchronizeOfflineModificationsOnFolderCreated.ts @@ -13,7 +13,7 @@ export class SynchronizeOfflineModificationsOnFolderCreated subscribedTo(): DomainEventClass[] { return [FolderCreatedDomainEvent]; } - on(domainEvent: FolderCreatedDomainEvent): Promise { - return this.synchronizeOfflineModifications.run(domainEvent.aggregateId); + async on(domainEvent: FolderCreatedDomainEvent): Promise { + await this.synchronizeOfflineModifications.run(domainEvent.aggregateId); } } diff --git a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts index 1548fcf3b..d6dc7c0e4 100644 --- a/src/workers/sync-engine/modules/folders/domain/FolderPath.ts +++ b/src/workers/sync-engine/modules/folders/domain/FolderPath.ts @@ -6,7 +6,7 @@ export class FolderPath extends Path { super(value); } - static fromParts(parts: Array) { + static fromParts(...parts: Array) { const full = path.posix.join(...parts); return new FolderPath(path.posix.normalize(full)); @@ -21,10 +21,10 @@ export class FolderPath extends Path { } updateName(name: string): FolderPath { - return FolderPath.fromParts([this.dirname(), name]); + return FolderPath.fromParts(this.dirname(), name); } changeFolder(folder: string): FolderPath { - return FolderPath.fromParts([folder, this.name()]); + return FolderPath.fromParts(folder, this.name()); } } diff --git a/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts b/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts index edc4b87f6..3dfe9ce8a 100644 --- a/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts @@ -6,6 +6,8 @@ import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; import { FolderUuid } from '../../domain/FolderUuid'; import { OfflineFolderMother } from '../domain/OfflineFolderMother'; import { FolderMother } from '../domain/FolderMother'; +import { FolderRenamedDomainEvent } from '../../domain/events/FolderRenamedDomainEvent'; +import { FolderPath } from '../../domain/FolderPath'; describe('Synchronize Offline Modifications', () => { let offlineRepository: InMemoryOfflineFolderRepository; @@ -49,19 +51,27 @@ describe('Synchronize Offline Modifications', () => { } }); - it('does nothing if the name of the online and offline folder is the same', async () => { + it('does nothing if the name of the online folder is not the previous one on the event', async () => { const offlineFolder = OfflineFolderMother.random(); - const folder = FolderMother.fromPartial(offlineFolder.attributes()); + + offlineFolder.rename( + FolderPath.fromParts(offlineFolder.dirname, offlineFolder.name + '!') + ); + + const folder = FolderMother.fromPartial({ + ...offlineFolder.attributes(), + path: offlineFolder.dirname + offlineFolder.name.repeat(1), + }); const offlineRepositorySyp = jest .spyOn(offlineRepository, 'getByUuid') .mockReturnValueOnce(offlineFolder); - const renamerSpy = jest.spyOn(renamer, 'run'); - repository.mockSearchByPartial.mockReturnValueOnce(folder); - SUT.run(offlineFolder.uuid); + const renamerSpy = jest.spyOn(renamer, 'run'); + + await SUT.run(offlineFolder.uuid); expect(offlineRepositorySyp).toBeCalledWith(offlineFolder.uuid); expect(repository.mockSearchByPartial).toBeCalledWith({ @@ -70,16 +80,13 @@ describe('Synchronize Offline Modifications', () => { expect(renamerSpy).not.toBeCalled(); }); - it('only renames the online folder if the modification data is older than the offline one', async () => { - const offlineFolder = OfflineFolderMother.fromPartial({ - updatedAt: new Date('2023-10-01').toISOString(), - path: '/new-name', - }); - const folder = FolderMother.fromPartial({ - ...offlineFolder.attributes(), - updatedAt: new Date('2000-10-01').toISOString(), - path: '/old-name', - }); + it('renames the online folder if the folder name is the previous one on the event', async () => { + const offlineFolder = OfflineFolderMother.random(); + const folder = FolderMother.fromPartial(offlineFolder.attributes()); + + offlineFolder.rename( + FolderPath.fromParts(offlineFolder.dirname, offlineFolder.name + '!') + ); jest .spyOn(offlineRepository, 'getByUuid') @@ -89,8 +96,38 @@ describe('Synchronize Offline Modifications', () => { repository.mockSearchByPartial.mockReturnValueOnce(folder); - SUT.run(offlineFolder.uuid); + await SUT.run(offlineFolder.uuid); expect(renamerSpy).toBeCalledWith(folder, offlineFolder.path); }); + + it('makes all the name changes recoded on the events', async () => { + const offlineFolder = OfflineFolderMother.random(); + const afterCreation = FolderMother.fromPartial(offlineFolder.attributes()); + + offlineFolder.rename( + FolderPath.fromParts(offlineFolder.dirname, offlineFolder.name + '!') + ); + const afterFirstRename = FolderMother.fromPartial( + offlineFolder.attributes() + ); + offlineFolder.rename( + FolderPath.fromParts(offlineFolder.dirname, offlineFolder.name + '!') + ); + + jest + .spyOn(offlineRepository, 'getByUuid') + .mockReturnValueOnce(offlineFolder); + + const renamerSpy = jest.spyOn(renamer, 'run'); + + repository.mockSearchByPartial + .mockReturnValueOnce(afterCreation) + .mockReturnValueOnce(afterFirstRename); + + const uuid = offlineFolder.uuid; + await SUT.run(uuid); + + expect(renamerSpy).toBeCalledTimes(2); + }); }); From 1dfd7492884dd86126829f12caf693bd08a82eb7 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 4 Oct 2023 17:08:30 +0200 Subject: [PATCH 15/15] fix: relative path on offline rename --- .../callbacks-controllers/buildControllers.ts | 1 + .../offline/OfflineRenameOrMoveController.ts | 20 +++++++++++++++---- .../Offline/OfflineFolderPathUpdater.ts | 0 .../folders/application/FolderPathUpdater.ts | 2 ++ .../Offline/OfflineFolderCreator.ts | 5 +---- .../SynchronizeOfflineModifications.test.ts | 1 - 6 files changed, 20 insertions(+), 9 deletions(-) delete mode 100644 src/workers/sync-engine/modules/files/application/Offline/OfflineFolderPathUpdater.ts diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index ecef9b23a..9ae3feea2 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -32,6 +32,7 @@ export function buildControllers(container: DependencyContainer) { ); const offlineRenameOrMoveController = new OfflineRenameOrMoveController( + container.absolutePathToRelativeConverter, container.offline.folderPathUpdater ); diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts index 2f4f193f4..f983a8e3e 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/offline/OfflineRenameOrMoveController.ts @@ -1,9 +1,14 @@ -import { OfflineFolderPathUpdater } from 'workers/sync-engine/modules/folders/application/Offline/OfflineFolderPathUpdater'; -import { CallbackController } from '../CallbackController'; import Logger from 'electron-log'; +import { OfflineFolderPathUpdater } from '../../../modules/folders/application/Offline/OfflineFolderPathUpdater'; +import { AbsolutePathToRelativeConverter } from '../../../modules/shared/application/AbsolutePathToRelativeConverter'; +import { PlatformPathConverter } from '../../../modules/shared/application/PlatformPathConverter'; +import { CallbackController } from '../CallbackController'; export class OfflineRenameOrMoveController extends CallbackController { - constructor(private readonly folderPathUpdater: OfflineFolderPathUpdater) { + constructor( + private readonly absolutePathToRelativeConverter: AbsolutePathToRelativeConverter, + private readonly folderPathUpdater: OfflineFolderPathUpdater + ) { super(); } @@ -20,6 +25,12 @@ export class OfflineRenameOrMoveController extends CallbackController { return callback(false); } + const win32RelativePath = + this.absolutePathToRelativeConverter.run(absolutePath); + + const posixRelativePath = + PlatformPathConverter.winToPosix(win32RelativePath); + if (this.isFilePlaceholder(trimmedId)) { Logger.error('Tried to rename or move an offline file'); return callback(false); @@ -27,7 +38,8 @@ export class OfflineRenameOrMoveController extends CallbackController { if (this.isFolderPlaceholder(trimmedId)) { const [_, folderUuid] = trimmedId.split(':'); - await this.folderPathUpdater.run(folderUuid, absolutePath); + await this.folderPathUpdater.run(folderUuid, posixRelativePath); + Logger.debug('OFFLINE FOLDER PATH UPDATED: ', folderUuid); return callback(true); } diff --git a/src/workers/sync-engine/modules/files/application/Offline/OfflineFolderPathUpdater.ts b/src/workers/sync-engine/modules/files/application/Offline/OfflineFolderPathUpdater.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts index 3b6c07ff4..ad70efb43 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts @@ -24,6 +24,7 @@ export class FolderPathUpdater { const desiredPath = new FolderPath(posixRelativePath); Logger.debug('desired path', desiredPath); + Logger.debug('folder', folder.attributes()); const dirnameChanged = folder.dirname !== desiredPath.dirname(); const nameChanged = folder.name !== desiredPath.name(); @@ -37,6 +38,7 @@ export class FolderPathUpdater { } if (nameChanged) { + Logger.debug('about to rename'); return await this.folderRenamer.run(folder, desiredPath); } diff --git a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts index cde71f750..2a73034aa 100644 --- a/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/Offline/OfflineFolderCreator.ts @@ -1,4 +1,3 @@ -import { PlatformPathConverter } from '../../../shared/application/PlatformPathConverter'; import { FolderPath } from '../../domain/FolderPath'; import { FolderRepository } from '../../domain/FolderRepository'; import { OfflineFolder } from '../../domain/OfflineFolder'; @@ -23,9 +22,7 @@ export class OfflineFolderCreator { throw new Error('The folder already exists on remote'); } - const parent = this.folderFinder.run( - PlatformPathConverter.winToPosix(folderPath.dirname()) - ); + const parent = this.folderFinder.run(folderPath.dirname()); const folder = OfflineFolder.create(folderPath, parent.id); diff --git a/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts b/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts index 3dfe9ce8a..189baad07 100644 --- a/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/SynchronizeOfflineModifications.test.ts @@ -6,7 +6,6 @@ import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; import { FolderUuid } from '../../domain/FolderUuid'; import { OfflineFolderMother } from '../domain/OfflineFolderMother'; import { FolderMother } from '../domain/FolderMother'; -import { FolderRenamedDomainEvent } from '../../domain/events/FolderRenamedDomainEvent'; import { FolderPath } from '../../domain/FolderPath'; describe('Synchronize Offline Modifications', () => {