From e7beaac6f826e9425773aaa560d2f0839a14f23d Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 26 Sep 2023 13:05:03 +0200 Subject: [PATCH 01/18] feat: create empty folders --- .../callbacks-controllers/buildControllers.ts | 3 +- .../controllers/AddFileController.ts | 16 +++++++- .../helpers/rawPathIsFolder.ts | 3 ++ .../DependencyContainer.ts | 4 +- .../DependencyContainerFactory.ts | 8 ++-- .../common/localRootFolderPath.ts | 15 +++++++ .../folders/FoldersContainer.ts | 9 ++++ .../dependency-injection/folders/builder.ts | 41 +++++++++++++++++++ .../modules/files/application/FileCreator.ts | 4 +- .../files/application/FilePathUpdater.ts | 4 +- .../infrastructure/HttpFileRepository.ts | 2 - .../test/application/FileCreator.test.ts | 4 +- .../test/application/FilePathUpdater.test.ts | 4 +- .../folders/application/FolderCreator.ts | 34 +++++++++++++++ ...{WebdavFolderFinder.ts => FolderFinder.ts} | 2 +- .../FolderPathFromAbsolutePathCreator.ts | 22 ++++++++++ .../application/WebdavFolderCreator.ts | 27 ------------ .../folders/application/WebdavFolderMover.ts | 4 +- .../application/WebdavFolderMover.test.ts | 6 +-- .../application/AllWebdavItemsSearcher.ts | 4 +- 20 files changed, 165 insertions(+), 51 deletions(-) create mode 100644 src/workers/sync-engine/callbacks-controllers/helpers/rawPathIsFolder.ts create mode 100644 src/workers/sync-engine/dependency-injection/common/localRootFolderPath.ts create mode 100644 src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts create mode 100644 src/workers/sync-engine/dependency-injection/folders/builder.ts create mode 100644 src/workers/sync-engine/modules/folders/application/FolderCreator.ts rename src/workers/sync-engine/modules/folders/application/{WebdavFolderFinder.ts => FolderFinder.ts} (95%) create mode 100644 src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts delete mode 100644 src/workers/sync-engine/modules/folders/application/WebdavFolderCreator.ts diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index 215b3c843..137d560da 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -10,7 +10,8 @@ export function buildControllers(container: DependencyContainer) { container.filePathFromAbsolutePathCreator, container.fileCreator, container.fileDeleter, - container.fileByPartialSearcher + container.fileByPartialSearcher, + container.folderCreator ); const renameOrMoveFileController = new RenameOrMoveController( diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts index 4d1a85939..8063c5ed5 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts @@ -6,6 +6,8 @@ import { RetryContentsUploader } from '../../modules/contents/application/RetryC import { FileDeleter } from '../../modules/files/application/FileDeleter'; import { FileByPartialSearcher } from '../../modules/files/application/FileByPartialSearcher'; import { PlatformPathConverter } from '../../modules/shared/test/helpers/PlatformPathConverter'; +import { rawPathIsFolder } from '../helpers/rawPathIsFolder'; +import { FolderCreator } from '../../modules/folders/application/FolderCreator'; export type DehydrateAndCreatePlaceholder = ( id: string, @@ -19,7 +21,8 @@ export class AddFileController extends CallbackController { private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, private readonly fileCreator: FileCreator, private readonly fileDeleter: FileDeleter, - private readonly searchByPartial: FileByPartialSearcher + private readonly searchByPartial: FileByPartialSearcher, + private readonly folderCreator: FolderCreator ) { super(); } @@ -28,6 +31,17 @@ export class AddFileController extends CallbackController { absolutePath: string, callback: (acknowledge: boolean, id: string) => void ): Promise { + if (rawPathIsFolder(absolutePath)) { + Logger.info('Crating folder', absolutePath); + try { + const id = await this.folderCreator.run(absolutePath); + return callback(true, id.toString()); + } catch (error: unknown) { + Logger.error('Error creating a folder: ', error); + return callback(false, ''); + } + } + try { const path = this.filePathFromAbsolutePathCreator.run(absolutePath); const file = this.searchByPartial.run({ diff --git a/src/workers/sync-engine/callbacks-controllers/helpers/rawPathIsFolder.ts b/src/workers/sync-engine/callbacks-controllers/helpers/rawPathIsFolder.ts new file mode 100644 index 000000000..be91952c9 --- /dev/null +++ b/src/workers/sync-engine/callbacks-controllers/helpers/rawPathIsFolder.ts @@ -0,0 +1,3 @@ +export function rawPathIsFolder(raw: string) { + return raw.endsWith('\\') || raw.endsWith('/'); +} diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainer.ts b/src/workers/sync-engine/dependency-injection/DependencyContainer.ts index ea5a9198f..52e428702 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainer.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainer.ts @@ -7,11 +7,13 @@ import { ItemsContainer } from './items/ItemsContainer'; import { ContentsContainer } from './contents/ContentsContainer'; import { FileCreator } from '../modules/files/application/FileCreator'; import { FilesContainer } from './files/FilesContainer'; +import { FoldersContainer } from './folders/FoldersContainer'; export interface DependencyContainer extends ItemsContainer, ContentsContainer, - FilesContainer { + FilesContainer, + FoldersContainer { fileCreator: FileCreator; filePathUpdater: FilePathUpdater; fileSearcher: FileSearcher; diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index c9fb3ec57..2fab933ea 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -11,7 +11,7 @@ import { FilePathUpdater } from '../modules/files/application/FilePathUpdater'; import { HttpFileRepository } from '../modules/files/infrastructure/HttpFileRepository'; import { FolderSearcher } from '../modules/folders/application/FolderSearcher'; import { WebdavFolderDeleter } from '../modules/folders/application/WebdavFolderDeleter'; -import { WebdavFolderFinder } from '../modules/folders/application/WebdavFolderFinder'; +import { FolderFinder } from '../modules/folders/application/FolderFinder'; import { HttpFolderRepository } from '../modules/folders/infrastructure/HttpFolderRepository'; import { Traverser } from '../modules/items/application/Traverser'; import { NodeJsEventBus } from '../modules/shared/infrastructure/DuplexEventBus'; @@ -19,6 +19,7 @@ import { DependencyContainer } from './DependencyContainer'; import { buildContentsContainer } from './contents/builder'; import { buildItemsContainer } from './items/builder'; import { buildFilesContainer } from './files/builder'; +import { buildFoldersContainer } from './folders/builder'; export class DependencyContainerFactory { private static _container: DependencyContainer | undefined; @@ -75,10 +76,11 @@ export class DependencyContainerFactory { const itemsContainer = buildItemsContainer(); const contentsContainer = await buildContentsContainer(); const filesContainer = await buildFilesContainer(); + const foldersContainer = await buildFoldersContainer(); const eventBus = new NodeJsEventBus(); - const folderFinder = new WebdavFolderFinder(folderRepository); + const folderFinder = new FolderFinder(folderRepository); const fileFinder = new FileFinderByContentsId(fileRepository); @@ -101,13 +103,13 @@ export class DependencyContainerFactory { ), folderSearcher: new FolderSearcher(folderRepository), - folderFinder, folderDeleter: new WebdavFolderDeleter(folderRepository), ...itemsContainer, ...contentsContainer, ...filesContainer, + ...foldersContainer, }; DependencyContainerFactory._container = container; diff --git a/src/workers/sync-engine/dependency-injection/common/localRootFolderPath.ts b/src/workers/sync-engine/dependency-injection/common/localRootFolderPath.ts new file mode 100644 index 000000000..ee8a1bb95 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/common/localRootFolderPath.ts @@ -0,0 +1,15 @@ +import configStore from 'main/config'; + +export class DependencyInjectionLocalRootFolderPath { + private static path: string; + + static get(): string { + if (DependencyInjectionLocalRootFolderPath.path) { + return DependencyInjectionLocalRootFolderPath.path; + } + + DependencyInjectionLocalRootFolderPath.path = configStore.get('syncRoot'); + + return DependencyInjectionLocalRootFolderPath.path; + } +} diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts new file mode 100644 index 000000000..9737267d4 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -0,0 +1,9 @@ +import { FolderFinder } from '../../modules/folders/application/FolderFinder'; +import { FolderCreator } from '../../modules/folders/application/FolderCreator'; +import { FolderPathFromAbsolutePathCreator } from '../../modules/folders/application/FolderPathFromAbsolutePathCreator'; + +export interface FoldersContainer { + folderCreator: FolderCreator; + folderFinder: FolderFinder; + folderPathFromAbsolutePathCreator: FolderPathFromAbsolutePathCreator; +} diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts new file mode 100644 index 000000000..837f5f6a8 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -0,0 +1,41 @@ +import { FolderCreator } from 'workers/sync-engine/modules/folders/application/FolderCreator'; +import { FoldersContainer } from './FoldersContainer'; +import { HttpFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository'; +import { DependencyInjectionHttpClientsProvider } from '../common/clients'; +import { DependencyInjectionTraverserProvider } from '../common/traverser'; +import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; +import { FolderFinder } from 'workers/sync-engine/modules/folders/application/FolderFinder'; +import { FolderPathFromAbsolutePathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator'; +import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; + +export async function buildFoldersContainer(): Promise { + const clients = DependencyInjectionHttpClientsProvider.get(); + const traverser = DependencyInjectionTraverserProvider.get(); + const rootFolderPath = DependencyInjectionLocalRootFolderPath.get(); + + const repository = new HttpFolderRepository( + clients.drive, + clients.newDrive, + traverser, + ipcRendererSyncEngine + ); + + await repository.init(); + const folderPathFromAbsolutePathCreator = + new FolderPathFromAbsolutePathCreator(rootFolderPath); + + const folderFinder = new FolderFinder(repository); + + const folderCreator = new FolderCreator( + folderPathFromAbsolutePathCreator, + repository, + folderFinder, + ipcRendererSyncEngine + ); + + return { + folderCreator, + folderFinder, + folderPathFromAbsolutePathCreator, + }; +} diff --git a/src/workers/sync-engine/modules/files/application/FileCreator.ts b/src/workers/sync-engine/modules/files/application/FileCreator.ts index 9f69238be..ccdc12ff8 100644 --- a/src/workers/sync-engine/modules/files/application/FileCreator.ts +++ b/src/workers/sync-engine/modules/files/application/FileCreator.ts @@ -1,4 +1,4 @@ -import { WebdavFolderFinder } from '../../folders/application/WebdavFolderFinder'; +import { FolderFinder } from '../../folders/application/FolderFinder'; import { FilePath } from '../domain/FilePath'; import { File } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; @@ -9,7 +9,7 @@ import { RemoteFileContents } from '../../contents/domain/RemoteFileContents'; export class FileCreator { constructor( private readonly repository: FileRepository, - private readonly folderFinder: WebdavFolderFinder, + private readonly folderFinder: FolderFinder, private readonly eventBus: WebdavServerEventBus ) {} diff --git a/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts b/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts index 6cc650cb4..3725195f4 100644 --- a/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts +++ b/src/workers/sync-engine/modules/files/application/FilePathUpdater.ts @@ -3,14 +3,14 @@ import { FileAlreadyExistsError } from '../domain/errors/FileAlreadyExistsError' import { FilePath } from '../domain/FilePath'; import { File } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; -import { WebdavFolderFinder } from '../../folders/application/WebdavFolderFinder'; +import { FolderFinder } from '../../folders/application/FolderFinder'; import { FileFinderByContentsId } from './FileFinderByContentsId'; export class FilePathUpdater { constructor( private readonly repository: FileRepository, private readonly fileFinderByContentsId: FileFinderByContentsId, - private readonly folderFinder: WebdavFolderFinder + private readonly folderFinder: FolderFinder ) {} private async rename(file: File, path: FilePath) { diff --git a/src/workers/sync-engine/modules/files/infrastructure/HttpFileRepository.ts b/src/workers/sync-engine/modules/files/infrastructure/HttpFileRepository.ts index 256b028f6..3c45a5175 100644 --- a/src/workers/sync-engine/modules/files/infrastructure/HttpFileRepository.ts +++ b/src/workers/sync-engine/modules/files/infrastructure/HttpFileRepository.ts @@ -15,7 +15,6 @@ import { RemoteItemsGenerator } from '../../items/application/RemoteItemsGenerat import { FileStatuses } from '../domain/FileStatus'; import { Crypt } from '../../shared/domain/Crypt'; import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; -import Logger from 'electron-log'; export class HttpFileRepository implements FileRepository { public files: Record = {}; @@ -75,7 +74,6 @@ export class HttpFileRepository implements FileRepository { const keys = Object.keys(partial) as Array>; const file = Object.values(this.files).find((file) => { - Logger.debug(file.attributes()[keys[0]], partial[keys[0]]); return keys.every((key) => file.attributes()[key] === partial[key]); }); 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 0780c3fb5..728976dd0 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 @@ -1,6 +1,6 @@ import { FolderMother } from '../../../folders/test/domain/FolderMother'; import { FolderRepositoryMock } from '../../../folders/test/__mocks__/FolderRepositoryMock'; -import { WebdavFolderFinder } from '../../../folders/application/WebdavFolderFinder'; +import { FolderFinder } from '../../../folders/application/FolderFinder'; import { FileCreator } from '../../application/FileCreator'; import { FileRepositoryMock } from '../__mocks__/FileRepositoryMock'; import { EventBusMock } from '../../../shared/test/__mock__/EventBusMock'; @@ -17,7 +17,7 @@ describe('File Creator', () => { beforeEach(() => { fileReposiotry = new FileRepositoryMock(); folderRepository = new FolderRepositoryMock(); - const folderFinder = new WebdavFolderFinder(folderRepository); + const folderFinder = new FolderFinder(folderRepository); eventBus = new EventBusMock(); SUT = new FileCreator(fileReposiotry, folderFinder, eventBus); 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 bff1c568c..59f315a38 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 @@ -2,7 +2,7 @@ import { FilePathUpdater } from '../../application/FilePathUpdater'; import { FilePath } from '../../domain/FilePath'; import { FileMother } from '../domain/FileMother'; import { FileRepositoryMock } from '../__mocks__/FileRepositoryMock'; -import { WebdavFolderFinder } from '../../../folders/application/WebdavFolderFinder'; +import { FolderFinder } from '../../../folders/application/FolderFinder'; import { FolderFinderMock } from '../../../folders/test/__mocks__/FolderFinderMock'; import { FileFinderByContentsId } from '../../application/FileFinderByContentsId'; @@ -20,7 +20,7 @@ describe('File path updater', () => { SUT = new FilePathUpdater( repository, fileFinderByContentsId, - folderFinder as unknown as WebdavFolderFinder + folderFinder as unknown as FolderFinder ); }); diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts new file mode 100644 index 000000000..62beea094 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -0,0 +1,34 @@ +import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; +import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; +import { Folder } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; +import { FolderFinder } from './FolderFinder'; +import { FolderPathFromAbsolutePathCreator } from './FolderPathFromAbsolutePathCreator'; + +export class FolderCreator { + constructor( + private readonly folderPathFromAbsolutePathCreator: FolderPathFromAbsolutePathCreator, + private readonly repository: FolderRepository, + private readonly folderFinder: FolderFinder, + private readonly ipc: SyncEngineIpc + ) {} + + async run(absolutePath: string): Promise { + const folderPath = this.folderPathFromAbsolutePathCreator.run(absolutePath); + this.ipc.send('CREATING_FOLDER', { + name: folderPath.name(), + }); + + const parent = this.folderFinder.run( + PlatformPathConverter.winToPosix(folderPath.dirname()) + ); + + const folder = await this.repository.create(folderPath, parent.id); + + this.ipc.send('FOLDER_CREATED', { + name: folderPath.name(), + }); + + return folder.id; + } +} diff --git a/src/workers/sync-engine/modules/folders/application/WebdavFolderFinder.ts b/src/workers/sync-engine/modules/folders/application/FolderFinder.ts similarity index 95% rename from src/workers/sync-engine/modules/folders/application/WebdavFolderFinder.ts rename to src/workers/sync-engine/modules/folders/application/FolderFinder.ts index d0f9f03de..b59ec3fae 100644 --- a/src/workers/sync-engine/modules/folders/application/WebdavFolderFinder.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderFinder.ts @@ -3,7 +3,7 @@ import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; -export class WebdavFolderFinder { +export class FolderFinder { constructor(private readonly repository: FolderRepository) {} run(path: string): Folder { diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts new file mode 100644 index 000000000..de338d74c --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts @@ -0,0 +1,22 @@ +import path from 'path'; +import { FolderPath } from '../domain/FolderPath'; + +export class FolderPathFromAbsolutePathCreator { + constructor(private readonly baseFolder: string) {} + + private calculateRelativePath(basePath: string, folderPath: string): string { + const relativePath = path.relative(basePath, folderPath); + const relativeFolders = path.dirname(relativePath); + const fileName = path.basename(folderPath); + + return path.join(relativeFolders, fileName); + } + + run(absolutePath: string): FolderPath { + const relative = this.calculateRelativePath(this.baseFolder, absolutePath); + + const withSlash = path.sep + relative; + + return new FolderPath(withSlash); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/WebdavFolderCreator.ts b/src/workers/sync-engine/modules/folders/application/WebdavFolderCreator.ts deleted file mode 100644 index bed3f5777..000000000 --- a/src/workers/sync-engine/modules/folders/application/WebdavFolderCreator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; -import { FolderPath } from '../domain/FolderPath'; -import { FolderRepository } from '../domain/FolderRepository'; -import { WebdavFolderFinder } from './WebdavFolderFinder'; - -export class WebdavFolderCreator { - constructor( - private readonly repository: FolderRepository, - private readonly folderFinder: WebdavFolderFinder, - private readonly ipc: SyncEngineIpc - ) {} - - async run(path: string): Promise { - const folderPath = new FolderPath(path); - this.ipc.send('CREATING_FOLDER', { - name: folderPath.name(), - }); - - const parent = this.folderFinder.run(folderPath.dirname()); - - await this.repository.create(folderPath, parent.id); - - this.ipc.send('FOLDER_CREATED', { - name: folderPath.name(), - }); - } -} diff --git a/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts b/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts index 9dff57cbd..eb87946f3 100644 --- a/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts +++ b/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts @@ -2,13 +2,13 @@ import { ActionNotPermitedError } from '../domain/errors/ActionNotPermitedError' import { FolderPath } from '../domain/FolderPath'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; -import { WebdavFolderFinder } from './WebdavFolderFinder'; +import { FolderFinder } from './FolderFinder'; import { WebdavFolderRenamer } from './WebdavFolderRenamer'; export class WebdavFolderMover { constructor( private readonly repository: FolderRepository, - private readonly folderFinder: WebdavFolderFinder, + private readonly folderFinder: FolderFinder, private readonly folderRenamer: WebdavFolderRenamer ) {} diff --git a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderMover.test.ts b/src/workers/sync-engine/modules/folders/test/application/WebdavFolderMover.test.ts index 37fc0c51a..e6b479cea 100644 --- a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderMover.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/WebdavFolderMover.test.ts @@ -1,4 +1,4 @@ -import { WebdavFolderFinder } from '../../application/WebdavFolderFinder'; +import { FolderFinder } from '../../application/FolderFinder'; import { WebdavFolderMover } from '../../application/WebdavFolderMover'; import { WebdavFolderRenamer } from '../../application/WebdavFolderRenamer'; import { FolderMother } from '../domain/FolderMother'; @@ -7,14 +7,14 @@ import { IpcRendererSyncEngineMock } from '../../../shared/test/__mock__/IpcRend describe('Folder Mover', () => { let repository: FolderRepositoryMock; - let folderFinder: WebdavFolderFinder; + let folderFinder: FolderFinder; let folderRenamer: WebdavFolderRenamer; let ipc: IpcRendererSyncEngineMock; let SUT: WebdavFolderMover; beforeEach(() => { repository = new FolderRepositoryMock(); - folderFinder = new WebdavFolderFinder(repository); + folderFinder = new FolderFinder(repository); ipc = new IpcRendererSyncEngineMock(); folderRenamer = new WebdavFolderRenamer(repository, ipc); diff --git a/src/workers/sync-engine/modules/shared/application/AllWebdavItemsSearcher.ts b/src/workers/sync-engine/modules/shared/application/AllWebdavItemsSearcher.ts index d1c74d802..7796e9035 100644 --- a/src/workers/sync-engine/modules/shared/application/AllWebdavItemsSearcher.ts +++ b/src/workers/sync-engine/modules/shared/application/AllWebdavItemsSearcher.ts @@ -1,12 +1,12 @@ import { FileRepository } from '../../files/domain/FileRepository'; -import { WebdavFolderFinder } from '../../folders/application/WebdavFolderFinder'; +import { FolderFinder } from '../../folders/application/FolderFinder'; import { FolderRepository } from '../../folders/domain/FolderRepository'; export class AllWebdavItemsNameLister { constructor( private readonly filesRepository: FileRepository, private readonly folderRepository: FolderRepository, - private readonly folderfinder: WebdavFolderFinder + private readonly folderfinder: FolderFinder ) {} async run(path: string): Promise> { From e464102aee9a7f237c5995d8ba2553134737f759 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 26 Sep 2023 16:46:53 +0200 Subject: [PATCH 02/18] feat: upload folders with contents --- .../callbacks-controllers/buildControllers.ts | 3 +- .../controllers/AddFileController.ts | 102 ++++++++++++++---- .../controllers/DeleteFileController.ts | 2 + .../DependencyContainerFactory.ts | 26 ++--- .../folders/FoldersContainer.ts | 6 +- .../dependency-injection/folders/builder.ts | 8 ++ .../folders/application/FolderCreator.ts | 4 +- .../FolderPathFromAbsolutePathCreator.ts | 4 +- .../infrastructure/HttpFolderRepository.ts | 17 ++- .../application/FolderPathCreator.test.ts | 20 ++++ .../folders/test/domain/FolderPath.test.ts | 2 +- .../modules/shared/domain/MapObserver.ts | 28 +++++ .../sync-engine/modules/shared/domain/Path.ts | 7 +- 13 files changed, 175 insertions(+), 54 deletions(-) create mode 100644 src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts create mode 100644 src/workers/sync-engine/modules/shared/domain/MapObserver.ts diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index 137d560da..40240281a 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -11,7 +11,8 @@ export function buildControllers(container: DependencyContainer) { container.fileCreator, container.fileDeleter, container.fileByPartialSearcher, - container.folderCreator + container.folderCreator, + container.folderFinder ); const renameOrMoveFileController = new RenameOrMoveController( diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts index 8063c5ed5..db759c820 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts @@ -8,6 +8,8 @@ import { FileByPartialSearcher } from '../../modules/files/application/FileByPar import { PlatformPathConverter } from '../../modules/shared/test/helpers/PlatformPathConverter'; import { rawPathIsFolder } from '../helpers/rawPathIsFolder'; import { FolderCreator } from '../../modules/folders/application/FolderCreator'; +import { MapObserver } from 'workers/sync-engine/modules/shared/domain/MapObserver'; +import { FolderFinder } from 'workers/sync-engine/modules/folders/application/FolderFinder'; export type DehydrateAndCreatePlaceholder = ( id: string, @@ -16,38 +18,28 @@ export type DehydrateAndCreatePlaceholder = ( ) => void; export class AddFileController extends CallbackController { - constructor( - private readonly contentsUploader: RetryContentsUploader, - private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, - private readonly fileCreator: FileCreator, - private readonly fileDeleter: FileDeleter, - private readonly searchByPartial: FileByPartialSearcher, - private readonly folderCreator: FolderCreator - ) { - super(); - } + private filesQueue = new Map< + string, + (acknowledge: boolean, id: string) => void + >(); - async execute( + private foldersQueue = new Map< + string, + (acknowledge: boolean, id: string) => void + >(); + + private createFile = async ( absolutePath: string, callback: (acknowledge: boolean, id: string) => void - ): Promise { - if (rawPathIsFolder(absolutePath)) { - Logger.info('Crating folder', absolutePath); - try { - const id = await this.folderCreator.run(absolutePath); - return callback(true, id.toString()); - } catch (error: unknown) { - Logger.error('Error creating a folder: ', error); - return callback(false, ''); - } - } - + ) => { try { const path = this.filePathFromAbsolutePathCreator.run(absolutePath); const file = this.searchByPartial.run({ path: PlatformPathConverter.winToPosix(path.value), }); + this.folderFinder.findFromFilePath(path); + const fileContents = await this.contentsUploader.run(absolutePath); if (file) { @@ -65,6 +57,70 @@ export class AddFileController extends CallbackController { } catch (error: unknown) { Logger.error('Error when adding a file: ', error); callback(false, ''); + } finally { + this.filesQueue.delete(absolutePath); + } + }; + + private createFiles = async () => { + for (const [absolutePath, callback] of this.filesQueue) { + await this.createFile(absolutePath, callback); + } + }; + + private createFolder = async ( + absolutePath: string, + callback: (acknowledge: boolean, id: string) => void + ) => { + Logger.info('Creating folder', absolutePath); + try { + const id = await this.folderCreator.run(absolutePath); + callback(true, id.toString()); + } catch (error: unknown) { + Logger.error('Error creating a folder: ', error); + callback(false, ''); + } finally { + this.foldersQueue.delete(absolutePath); + } + }; + + private createFolders = async () => { + for (const [absolutePath, callback] of this.foldersQueue) { + await this.createFolder(absolutePath, callback); + } + }; + + private readonly obsever = new MapObserver( + this.foldersQueue, + this.createFiles + ); + + constructor( + private readonly contentsUploader: RetryContentsUploader, + private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, + private readonly fileCreator: FileCreator, + private readonly fileDeleter: FileDeleter, + private readonly searchByPartial: FileByPartialSearcher, + private readonly folderCreator: FolderCreator, + private readonly folderFinder: FolderFinder + ) { + super(); + } + + async execute( + absolutePath: string, + callback: (acknowledge: boolean, id: string) => void + ): Promise { + if (rawPathIsFolder(absolutePath)) { + this.foldersQueue.set(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) { + this.createFiles(); } } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts index aed4dd58d..382958e3c 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts @@ -9,6 +9,8 @@ export class DeleteFileController extends CallbackController { async execute(contentsId: string) { const trimmedId = this.trim(contentsId); + if() + await this.deleter.run(trimmedId); } } diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index 2fab933ea..b7711dc1b 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -9,10 +9,6 @@ import { FilePathFromAbsolutePathCreator } from '../modules/files/application/Fi import { FileSearcher } from '../modules/files/application/FileSearcher'; import { FilePathUpdater } from '../modules/files/application/FilePathUpdater'; import { HttpFileRepository } from '../modules/files/infrastructure/HttpFileRepository'; -import { FolderSearcher } from '../modules/folders/application/FolderSearcher'; -import { WebdavFolderDeleter } from '../modules/folders/application/WebdavFolderDeleter'; -import { FolderFinder } from '../modules/folders/application/FolderFinder'; -import { HttpFolderRepository } from '../modules/folders/infrastructure/HttpFolderRepository'; import { Traverser } from '../modules/items/application/Traverser'; import { NodeJsEventBus } from '../modules/shared/infrastructure/DuplexEventBus'; import { DependencyContainer } from './DependencyContainer'; @@ -63,15 +59,7 @@ export class DependencyContainerFactory { ipcRendererSyncEngine ); - const folderRepository = new HttpFolderRepository( - clients.drive, - clients.newDrive, - traverser, - ipcRendererSyncEngine - ); - await fileRepository.init(); - await folderRepository.init(); const itemsContainer = buildItemsContainer(); const contentsContainer = await buildContentsContainer(); @@ -80,21 +68,23 @@ export class DependencyContainerFactory { const eventBus = new NodeJsEventBus(); - const folderFinder = new FolderFinder(folderRepository); - const fileFinder = new FileFinderByContentsId(fileRepository); const filePathUpdater = new FilePathUpdater( fileRepository, fileFinder, - folderFinder + foldersContainer.folderFinder ); const container = { drive: clients.drive, newDrive: clients.newDrive, - fileCreator: new FileCreator(fileRepository, folderFinder, eventBus), + fileCreator: new FileCreator( + fileRepository, + foldersContainer.folderFinder, + eventBus + ), filePathUpdater, fileSearcher: new FileSearcher(fileRepository), @@ -102,10 +92,6 @@ export class DependencyContainerFactory { localRootFolderPath ), - folderSearcher: new FolderSearcher(folderRepository), - - folderDeleter: new WebdavFolderDeleter(folderRepository), - ...itemsContainer, ...contentsContainer, ...filesContainer, diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index 9737267d4..00b36cf70 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -1,9 +1,13 @@ -import { FolderFinder } from '../../modules/folders/application/FolderFinder'; import { FolderCreator } from '../../modules/folders/application/FolderCreator'; +import { FolderFinder } from '../../modules/folders/application/FolderFinder'; import { FolderPathFromAbsolutePathCreator } from '../../modules/folders/application/FolderPathFromAbsolutePathCreator'; +import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; +import { WebdavFolderDeleter } from '../../modules/folders/application/WebdavFolderDeleter'; export interface FoldersContainer { folderCreator: FolderCreator; folderFinder: FolderFinder; folderPathFromAbsolutePathCreator: FolderPathFromAbsolutePathCreator; + folderSearcher: FolderSearcher; + folderDeleter: WebdavFolderDeleter; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 837f5f6a8..4625f5e42 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -7,6 +7,8 @@ import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; import { FolderFinder } from 'workers/sync-engine/modules/folders/application/FolderFinder'; import { FolderPathFromAbsolutePathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator'; import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; +import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; +import { WebdavFolderDeleter } from 'workers/sync-engine/modules/folders/application/WebdavFolderDeleter'; export async function buildFoldersContainer(): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); @@ -26,6 +28,10 @@ export async function buildFoldersContainer(): Promise { const folderFinder = new FolderFinder(repository); + const folderSearcher = new FolderSearcher(repository); + + const folderDeleter = new WebdavFolderDeleter(repository); + const folderCreator = new FolderCreator( folderPathFromAbsolutePathCreator, repository, @@ -37,5 +43,7 @@ export async function buildFoldersContainer(): Promise { folderCreator, folderFinder, folderPathFromAbsolutePathCreator, + folderSearcher, + folderDeleter, }; } diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index 62beea094..e0a1a7060 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -14,7 +14,9 @@ export class FolderCreator { ) {} async run(absolutePath: string): Promise { - const folderPath = this.folderPathFromAbsolutePathCreator.run(absolutePath); + const folderPath = this.folderPathFromAbsolutePathCreator.run( + PlatformPathConverter.winToPosix(absolutePath) + ); this.ipc.send('CREATING_FOLDER', { name: folderPath.name(), }); diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts index de338d74c..6241d2bfa 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts @@ -13,10 +13,10 @@ export class FolderPathFromAbsolutePathCreator { } run(absolutePath: string): FolderPath { - const relative = this.calculateRelativePath(this.baseFolder, absolutePath); + const sanitized = absolutePath.replace('\\\\', '\\'); + const relative = this.calculateRelativePath(this.baseFolder, sanitized); const withSlash = path.sep + relative; - return new FolderPath(withSlash); } } diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index 8fec20bf3..167edf8f1 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts @@ -43,13 +43,18 @@ export class HttpFolderRepository implements FolderRepository { ) as Array<[string, Folder]>; this.folders = folders.reduce((items, [key, value]) => { - items[key] = value; + if (items[key] === undefined) { + items[key] = value; + } else if (value.updatedAt > items[key].updatedAt) { + items[key] = value; + } return items; - }, {} as Record); + }, this.folders); } search(path: string): Nullable { + Logger.debug(Object.keys(this.folders)); return this.folders[path]; } @@ -69,13 +74,13 @@ export class HttpFolderRepository implements FolderRepository { ); if (response.status !== 201) { - throw new Error('Folder creation failded'); + throw new Error('Folder creation failed'); } const serverFolder = response.data as ServerFolder | null; if (!serverFolder) { - throw new Error('Folder creation failded, no data returned'); + throw new Error('Folder creation failed, no data returned'); } const folder = Folder.create({ @@ -88,6 +93,10 @@ export class HttpFolderRepository implements FolderRepository { status: FolderStatuses.EXISTS, }); + const p = folder.path.value.replace('\\', '/'); + + this.folders[p] = folder; + return folder; } 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 new file mode 100644 index 000000000..48edf9840 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts @@ -0,0 +1,20 @@ +import path from 'path'; +import { FolderPathFromAbsolutePathCreator } from '../../application/FolderPathFromAbsolutePathCreator'; + +describe('Folder Phat Creator', () => { + describe('Create from absolute', () => { + it('works', () => { + const ab = 'C\\:Users\\JWcer\\InternxtDrive\\\\New folder (4)\\'; + + const sut = new FolderPathFromAbsolutePathCreator( + 'C\\:Users\\JWcer\\InternxtDrive' + ); + + const result = sut.run(ab); + + 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 13a64820c..d4a595f1d 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 @@ -3,7 +3,7 @@ import { PlatformPathConverter } from '../../../shared/test/helpers/PlatformPath import path from 'path'; describe('Path', () => { - describe('path instanciation', () => { + describe('path instantiation', () => { it('path from parts creates expected result', () => { const parts = [path.sep, 'Family']; diff --git a/src/workers/sync-engine/modules/shared/domain/MapObserver.ts b/src/workers/sync-engine/modules/shared/domain/MapObserver.ts new file mode 100644 index 000000000..f3c2a0a74 --- /dev/null +++ b/src/workers/sync-engine/modules/shared/domain/MapObserver.ts @@ -0,0 +1,28 @@ +export class MapObserver { + constructor( + private readonly mapToObserve: Map, + private readonly callback: () => void, + private intervalId: NodeJS.Timeout | null = null + ) { + this.startObserving(); + } + + private startObserving() { + if (this.intervalId === null) { + this.intervalId = setInterval(() => { + if (this.mapToObserve.size === 0) { + clearInterval(this.intervalId!); + this.intervalId = null; + this.callback(); + } + }, 1_000); + } + } + + stopObserving() { + if (this.intervalId !== null) { + clearInterval(this.intervalId); + this.intervalId = null; + } + } +} diff --git a/src/workers/sync-engine/modules/shared/domain/Path.ts b/src/workers/sync-engine/modules/shared/domain/Path.ts index 2151b26d8..5ec02d867 100644 --- a/src/workers/sync-engine/modules/shared/domain/Path.ts +++ b/src/workers/sync-engine/modules/shared/domain/Path.ts @@ -21,7 +21,12 @@ export abstract class Path extends ValueObject { } dirname(): string { - return this.convertPathToCurrentPlatform(path.dirname(this.value)); + const dirname = this.convertPathToCurrentPlatform(path.dirname(this.value)); + if (dirname === '.') { + return path.sep; + } + + return dirname; } posixDirname(): string { From a57a11fad054bad7412e91b0149d606048bdbcb6 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 26 Sep 2023 18:42:13 +0200 Subject: [PATCH 03/18] feat: delete folder flattering the contents on trash --- src/workers/sync-engine/BindingManager.ts | 2 + .../callbacks-controllers/buildControllers.ts | 5 +- .../controllers/AddFileController.ts | 4 +- .../controllers/DeleteFileController.ts | 27 ++++++++-- .../DependencyContainerFactory.ts | 50 +------------------ .../files/FilesContainer.ts | 8 +++ .../dependency-injection/files/builder.ts | 36 ++++++++++++- .../folders/FoldersContainer.ts | 4 +- .../dependency-injection/folders/builder.ts | 4 +- .../folders/application/FolderCreator.ts | 4 +- .../folders/application/FolderDeleter.ts | 18 +++++++ .../application/WebdavFolderDeleter.ts | 11 ---- .../modules/folders/domain/Folder.ts | 14 ++++++ .../folders/domain/FolderRepository.ts | 2 + .../infrastructure/HttpFolderRepository.ts | 17 ++++++- .../application/WebdavFolderDeleter.test.ts | 6 +-- 16 files changed, 135 insertions(+), 77 deletions(-) create mode 100644 src/workers/sync-engine/modules/folders/application/FolderDeleter.ts delete mode 100644 src/workers/sync-engine/modules/folders/application/WebdavFolderDeleter.ts diff --git a/src/workers/sync-engine/BindingManager.ts b/src/workers/sync-engine/BindingManager.ts index 03f78869b..d07500ab1 100644 --- a/src/workers/sync-engine/BindingManager.ts +++ b/src/workers/sync-engine/BindingManager.ts @@ -48,9 +48,11 @@ export class BindingsManager { this.controllers.deleteFile .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/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index 40240281a..0576320fe 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -21,7 +21,10 @@ export function buildControllers(container: DependencyContainer) { container.fileDeleter ); - const deleteFileController = new DeleteFileController(container.fileDeleter); + const deleteFileController = new DeleteFileController( + container.fileDeleter, + container.folderDeleter + ); const downloadFileController = new DownloadFileController( container.fileFinderByContentsId, diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts index db759c820..a82d8c4bd 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts @@ -74,8 +74,8 @@ export class AddFileController extends CallbackController { ) => { Logger.info('Creating folder', absolutePath); try { - const id = await this.folderCreator.run(absolutePath); - callback(true, id.toString()); + const folder = await this.folderCreator.run(absolutePath); + callback(true, folder.uuid); } catch (error: unknown) { Logger.error('Error creating a folder: ', error); callback(false, ''); diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts index 382958e3c..047a949c4 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts @@ -1,16 +1,37 @@ +import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; import { FileDeleter } from '../../modules/files/application/FileDeleter'; import { CallbackController } from './CallbackController'; +import Logger from 'electron-log'; export class DeleteFileController extends CallbackController { - constructor(private readonly deleter: FileDeleter) { + constructor( + private readonly fileDeleter: FileDeleter, + private readonly folderDeleter: FolderDeleter + ) { super(); } + private async deleteFile(id: string) { + Logger.debug('Deleting file'); + await this.fileDeleter.run(id); + Logger.debug('file deleted'); + } + + private async deleteFolder(uuid: string) { + Logger.debug('Deleting folder'); + await this.folderDeleter.run(uuid); + Logger.debug('Folder deleted'); + } + async execute(contentsId: string) { const trimmedId = this.trim(contentsId); - if() + if (trimmedId.length === 24) { + await this.deleteFile(trimmedId); + return; + } - await this.deleter.run(trimmedId); + await this.deleteFolder(trimmedId); + return; } } diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index b7711dc1b..2ac1aaf6b 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -1,16 +1,5 @@ import { getUser } from 'main/auth/service'; -import configStore from 'main/config'; import { getClients } from '../../../shared/HttpClient/backgroud-process-clients'; -import crypt from '../../utils/crypt'; -import { ipcRendererSyncEngine } from '../ipcRendererSyncEngine'; -import { FileCreator } from '../modules/files/application/FileCreator'; -import { FileFinderByContentsId } from '../modules/files/application/FileFinderByContentsId'; -import { FilePathFromAbsolutePathCreator } from '../modules/files/application/FilePathFromAbsolutePathCreator'; -import { FileSearcher } from '../modules/files/application/FileSearcher'; -import { FilePathUpdater } from '../modules/files/application/FilePathUpdater'; -import { HttpFileRepository } from '../modules/files/infrastructure/HttpFileRepository'; -import { Traverser } from '../modules/items/application/Traverser'; -import { NodeJsEventBus } from '../modules/shared/infrastructure/DuplexEventBus'; import { DependencyContainer } from './DependencyContainer'; import { buildContentsContainer } from './contents/builder'; import { buildItemsContainer } from './items/builder'; @@ -46,52 +35,15 @@ export class DependencyContainerFactory { const clients = getClients(); - const localRootFolderPath = configStore.get('syncRoot'); - - const traverser = new Traverser(crypt, user.root_folder_id); - - const fileRepository = new HttpFileRepository( - crypt, - clients.drive, - clients.newDrive, - traverser, - user.bucket, - ipcRendererSyncEngine - ); - - await fileRepository.init(); - const itemsContainer = buildItemsContainer(); const contentsContainer = await buildContentsContainer(); - const filesContainer = await buildFilesContainer(); const foldersContainer = await buildFoldersContainer(); - - const eventBus = new NodeJsEventBus(); - - const fileFinder = new FileFinderByContentsId(fileRepository); - - const filePathUpdater = new FilePathUpdater( - fileRepository, - fileFinder, - foldersContainer.folderFinder - ); + const filesContainer = await buildFilesContainer(foldersContainer); const container = { drive: clients.drive, newDrive: clients.newDrive, - fileCreator: new FileCreator( - fileRepository, - foldersContainer.folderFinder, - eventBus - ), - - filePathUpdater, - fileSearcher: new FileSearcher(fileRepository), - filePathFromAbsolutePathCreator: new FilePathFromAbsolutePathCreator( - localRootFolderPath - ), - ...itemsContainer, ...contentsContainer, ...filesContainer, diff --git a/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts b/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts index 633cf7a90..e0115c7ef 100644 --- a/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts +++ b/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts @@ -1,6 +1,10 @@ import { FileByPartialSearcher } from '../../modules/files/application/FileByPartialSearcher'; +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'; export interface FilesContainer { @@ -8,4 +12,8 @@ export interface FilesContainer { localRepositoryRefresher: LocalRepositoryRepositoryRefresher; fileDeleter: FileDeleter; fileByPartialSearcher: FileByPartialSearcher; + filePathUpdater: FilePathUpdater; + fileCreator: FileCreator; + filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator; + fileSearcher: FileSearcher; } diff --git a/src/workers/sync-engine/dependency-injection/files/builder.ts b/src/workers/sync-engine/dependency-injection/files/builder.ts index 948df7709..c0df25a3a 100644 --- a/src/workers/sync-engine/dependency-injection/files/builder.ts +++ b/src/workers/sync-engine/dependency-injection/files/builder.ts @@ -1,19 +1,31 @@ import crypt from '../../../utils/crypt'; import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; import { FileByPartialSearcher } from '../../modules/files/application/FileByPartialSearcher'; +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 { FolderFinder } from '../../modules/folders/application/FolderFinder'; +import { NodeJsEventBus } from '../../modules/shared/infrastructure/DuplexEventBus'; import { DependencyInjectionHttpClientsProvider } from '../common/clients'; +import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { DependencyInjectionTraverserProvider } from '../common/traverser'; import { DependencyInjectionUserProvider } from '../common/user'; import { FilesContainer } from './FilesContainer'; -export async function buildFilesContainer(): Promise { +export async function buildFilesContainer({ + folderFinder, +}: { + folderFinder: FolderFinder; +}): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); const user = DependencyInjectionUserProvider.get(); + const localRootFolderPath = DependencyInjectionLocalRootFolderPath.get(); const fileRepository = new HttpFileRepository( crypt, @@ -41,11 +53,33 @@ export async function buildFilesContainer(): Promise { const fileByPartialSearcher = new FileByPartialSearcher(fileRepository); + const filePathUpdater = new FilePathUpdater( + fileRepository, + fileFinderByContentsId, + folderFinder + ); + + const fileCreator = new FileCreator( + fileRepository, + folderFinder, + new NodeJsEventBus() + ); + + const filePathFromAbsolutePathCreator = new FilePathFromAbsolutePathCreator( + localRootFolderPath + ); + + const fileSearcher = new FileSearcher(fileRepository); + const container: FilesContainer = { fileFinderByContentsId, localRepositoryRefresher: localRepositoryRefresher, fileDeleter, fileByPartialSearcher, + filePathUpdater, + fileCreator, + filePathFromAbsolutePathCreator, + fileSearcher, }; return container; diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index 00b36cf70..ce403909d 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -2,12 +2,12 @@ import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { FolderFinder } from '../../modules/folders/application/FolderFinder'; import { FolderPathFromAbsolutePathCreator } from '../../modules/folders/application/FolderPathFromAbsolutePathCreator'; import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; -import { WebdavFolderDeleter } from '../../modules/folders/application/WebdavFolderDeleter'; +import { FolderDeleter } from '../../modules/folders/application/FolderDeleter'; export interface FoldersContainer { folderCreator: FolderCreator; folderFinder: FolderFinder; folderPathFromAbsolutePathCreator: FolderPathFromAbsolutePathCreator; folderSearcher: FolderSearcher; - folderDeleter: WebdavFolderDeleter; + folderDeleter: FolderDeleter; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 4625f5e42..8453f5aa9 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -8,7 +8,7 @@ import { FolderFinder } from 'workers/sync-engine/modules/folders/application/Fo import { FolderPathFromAbsolutePathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator'; import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; -import { WebdavFolderDeleter } from 'workers/sync-engine/modules/folders/application/WebdavFolderDeleter'; +import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; export async function buildFoldersContainer(): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); @@ -30,7 +30,7 @@ export async function buildFoldersContainer(): Promise { const folderSearcher = new FolderSearcher(repository); - const folderDeleter = new WebdavFolderDeleter(repository); + const folderDeleter = new FolderDeleter(repository); const folderCreator = new FolderCreator( folderPathFromAbsolutePathCreator, diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index e0a1a7060..bc55e6793 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -13,7 +13,7 @@ export class FolderCreator { private readonly ipc: SyncEngineIpc ) {} - async run(absolutePath: string): Promise { + async run(absolutePath: string): Promise { const folderPath = this.folderPathFromAbsolutePathCreator.run( PlatformPathConverter.winToPosix(absolutePath) ); @@ -31,6 +31,6 @@ export class FolderCreator { name: folderPath.name(), }); - return folder.id; + return folder; } } diff --git a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts new file mode 100644 index 000000000..b203ea075 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts @@ -0,0 +1,18 @@ +import { Folder } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; +import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; + +export class FolderDeleter { + constructor(private readonly repository: FolderRepository) {} + + async run(uuid: Folder['uuid']): Promise { + const folder = this.repository.searchByPartial({ uuid }); + + if (!folder) { + throw new FolderNotFoundError(uuid); + } + + folder.trash(); + await this.repository.trash(folder); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/WebdavFolderDeleter.ts b/src/workers/sync-engine/modules/folders/application/WebdavFolderDeleter.ts deleted file mode 100644 index 31577a5a9..000000000 --- a/src/workers/sync-engine/modules/folders/application/WebdavFolderDeleter.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Folder } from '../domain/Folder'; -import { FolderRepository } from '../domain/FolderRepository'; - -export class WebdavFolderDeleter { - constructor(private readonly repository: FolderRepository) {} - - async run(folder: Folder): Promise { - folder.trash(); - await this.repository.trash(folder); - } -} diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index ed4bff58f..f1559c85f 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -168,4 +168,18 @@ export class Folder extends AggregateRoot { return attributes; } + + attributes(): FolderAttributes { + const attributes: FolderAttributes = { + id: this.id, + uuid: this.uuid, + parentId: this._parentId || 0, + 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/FolderRepository.ts b/src/workers/sync-engine/modules/folders/domain/FolderRepository.ts index 3c2b6474b..6735c624d 100644 --- a/src/workers/sync-engine/modules/folders/domain/FolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/domain/FolderRepository.ts @@ -5,6 +5,8 @@ import { FolderPath } from './FolderPath'; export interface FolderRepository { search(path: string): Nullable; + searchByPartial(partial: Partial): Nullable; + create( name: FolderPath, parentId: FolderAttributes['parentId'] diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index 167edf8f1..5a318558f 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts @@ -4,7 +4,7 @@ import { ServerFile } from '../../../../filesystems/domain/ServerFile'; import { ServerFolder } from '../../../../filesystems/domain/ServerFolder'; import { Traverser } from '../../items/application/Traverser'; import { FolderPath } from '../domain/FolderPath'; -import { Folder } from '../domain/Folder'; +import { Folder, FolderAttributes } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import Logger from 'electron-log'; import * as uuid from 'uuid'; @@ -58,6 +58,21 @@ export class HttpFolderRepository implements FolderRepository { return this.folders[path]; } + searchByPartial(partial: Partial): Nullable { + const keys = Object.keys(partial) as Array>; + + const folder = Object.values(this.folders).find((folder) => { + // Logger.debug(folder.attributes()[keys[0]], partial[keys[0]]); + return keys.every((key) => folder.attributes()[key] === partial[key]); + }); + + if (folder) { + return Folder.from(folder.attributes()); + } + + return undefined; + } + async create(path: FolderPath, parentId: number): Promise { const plainName = path.name(); diff --git a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderDeleter.test.ts b/src/workers/sync-engine/modules/folders/test/application/WebdavFolderDeleter.test.ts index 79cb5efc0..c6e6334b6 100644 --- a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderDeleter.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/WebdavFolderDeleter.test.ts @@ -1,15 +1,15 @@ -import { WebdavFolderDeleter } from '../../application/WebdavFolderDeleter'; +import { FolderDeleter } from '../../application/FolderDeleter'; import { FolderStatus } from '../../domain/FolderStatus'; import { FolderMother } from '../domain/FolderMother'; import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; describe('Folder deleter', () => { let repository: FolderRepositoryMock; - let SUT: WebdavFolderDeleter; + let SUT: FolderDeleter; beforeEach(() => { repository = new FolderRepositoryMock(); - SUT = new WebdavFolderDeleter(repository); + SUT = new FolderDeleter(repository); }); it('trashes an existing folder', () => { From 49151f6d71c4f4b566e2dd81ff8bcd3f464890ec Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 26 Sep 2023 18:45:10 +0200 Subject: [PATCH 04/18] fix: update updated at on trash --- src/workers/sync-engine/modules/folders/domain/Folder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index f1559c85f..2668705e2 100644 --- a/src/workers/sync-engine/modules/folders/domain/Folder.ts +++ b/src/workers/sync-engine/modules/folders/domain/Folder.ts @@ -135,6 +135,7 @@ export class Folder extends AggregateRoot { trash() { this._status = this._status.changeTo(FolderStatuses.TRASHED); + this.updatedAt = new Date(); // TODO: recored trashed event } From a4a509f0841937032671b02579c66f232178e499 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 26 Sep 2023 18:46:08 +0200 Subject: [PATCH 05/18] chore: rename delete controller --- src/workers/sync-engine/BindingManager.ts | 2 +- .../sync-engine/callbacks-controllers/buildControllers.ts | 6 +++--- .../{DeleteFileController.ts => DeleteController.ts} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/workers/sync-engine/callbacks-controllers/controllers/{DeleteFileController.ts => DeleteController.ts} (93%) diff --git a/src/workers/sync-engine/BindingManager.ts b/src/workers/sync-engine/BindingManager.ts index d07500ab1..846c0edde 100644 --- a/src/workers/sync-engine/BindingManager.ts +++ b/src/workers/sync-engine/BindingManager.ts @@ -45,7 +45,7 @@ export class BindingsManager { contentsId: string, callback: (response: boolean) => void ) => { - this.controllers.deleteFile + this.controllers.delete .execute(contentsId) .then(() => { Logger.debug('DELETE RESPONSE SUCCESSFUL'); diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index 0576320fe..157548a7a 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -1,6 +1,6 @@ import { DependencyContainer } from '../dependency-injection/DependencyContainer'; import { AddFileController } from './controllers/AddFileController'; -import { DeleteFileController } from './controllers/DeleteFileController'; +import { DeleteController } from './controllers/DeleteController'; import { DownloadFileController } from './controllers/DownloadFileController'; import { RenameOrMoveController } from './controllers/RenameOrMoveController'; @@ -21,7 +21,7 @@ export function buildControllers(container: DependencyContainer) { container.fileDeleter ); - const deleteFileController = new DeleteFileController( + const deleteController = new DeleteController( container.fileDeleter, container.folderDeleter ); @@ -35,7 +35,7 @@ export function buildControllers(container: DependencyContainer) { return { addFile: addFileController, renameOrMoveFile: renameOrMoveFileController, - deleteFile: deleteFileController, + delete: deleteController, downloadFile: downloadFileController, } as const; } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts similarity index 93% rename from src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts rename to src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index 047a949c4..e76026d78 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -3,7 +3,7 @@ import { FileDeleter } from '../../modules/files/application/FileDeleter'; import { CallbackController } from './CallbackController'; import Logger from 'electron-log'; -export class DeleteFileController extends CallbackController { +export class DeleteController extends CallbackController { constructor( private readonly fileDeleter: FileDeleter, private readonly folderDeleter: FolderDeleter From 6d2125098dd96d0562a4c6857e82fb2480cd5e7b Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 26 Sep 2023 19:23:32 +0200 Subject: [PATCH 06/18] feat: delete files with a delay --- .../callbacks-controllers/buildControllers.ts | 12 +++---- .../controllers/DeleteController.ts | 17 ++++++---- .../controllers/RenameOrMoveController.ts | 5 +-- .../modules/shared/domain/DelayQueue.ts | 33 +++++++++++++++++++ 4 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 src/workers/sync-engine/modules/shared/domain/DelayQueue.ts diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index 157548a7a..470ca76f4 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -15,17 +15,17 @@ export function buildControllers(container: DependencyContainer) { container.folderFinder ); - const renameOrMoveFileController = new RenameOrMoveController( - container.filePathFromAbsolutePathCreator, - container.filePathUpdater, - container.fileDeleter - ); - const deleteController = new DeleteController( container.fileDeleter, container.folderDeleter ); + const renameOrMoveFileController = new RenameOrMoveController( + container.filePathFromAbsolutePathCreator, + container.filePathUpdater, + deleteController + ); + const downloadFileController = new DownloadFileController( container.fileFinderByContentsId, container.contentsDownloader, diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index e76026d78..057351892 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -2,19 +2,24 @@ import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/F import { FileDeleter } from '../../modules/files/application/FileDeleter'; import { CallbackController } from './CallbackController'; import Logger from 'electron-log'; +import { DelayQueue } from 'workers/sync-engine/modules/shared/domain/DelayQueue'; export class DeleteController extends CallbackController { + private readonly filesQueue: DelayQueue; + constructor( private readonly fileDeleter: FileDeleter, private readonly folderDeleter: FolderDeleter ) { super(); - } - private async deleteFile(id: string) { - Logger.debug('Deleting file'); - await this.fileDeleter.run(id); - Logger.debug('file deleted'); + const deleteAllFiles = async (files: Array) => { + Logger.debug('Deleting queued files'); + await Promise.all(files.map((f) => this.fileDeleter.run(f))); + Logger.debug('Queued files deleted'); + }; + + this.filesQueue = new DelayQueue(deleteAllFiles); } private async deleteFolder(uuid: string) { @@ -27,7 +32,7 @@ export class DeleteController extends CallbackController { const trimmedId = this.trim(contentsId); if (trimmedId.length === 24) { - await this.deleteFile(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 5bd83e9a6..4f32a4ba8 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts @@ -2,12 +2,13 @@ import { FileDeleter } from 'workers/sync-engine/modules/files/application/FileD import { FilePathFromAbsolutePathCreator } from '../../modules/files/application/FilePathFromAbsolutePathCreator'; import { FilePathUpdater } from '../../modules/files/application/FilePathUpdater'; import { CallbackController } from './CallbackController'; +import { DeleteController } from './DeleteController'; export class RenameOrMoveController extends CallbackController { constructor( private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, private readonly filePathUpdater: FilePathUpdater, - private readonly fileDeleter: FileDeleter + private readonly deleteController: DeleteController ) { super(); } @@ -21,7 +22,7 @@ export class RenameOrMoveController extends CallbackController { try { if (absolutePath.startsWith('\\$Recycle.Bin')) { - await this.fileDeleter.run(trimmedId); + await this.deleteController.execute(trimmedId); callback(true); return; } diff --git a/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts new file mode 100644 index 000000000..e5f8cc5e2 --- /dev/null +++ b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts @@ -0,0 +1,33 @@ +export class DelayQueue { + private static readonly DELAY = 3_000; + private queue: Array; + private timeout: NodeJS.Timeout | null = null; + + constructor( + private readonly loopQueue: (queue: Array) => Promise + ) { + this.queue = []; + } + + push(value: string) { + this.clearTimeout(); + + this.timeout = setTimeout(async () => { + await this.loopQueue(this.queue); + }, DelayQueue.DELAY); + + this.queue.push(value); + } + + private clearTimeout() { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + } + + clear() { + this.clearTimeout(); + this.queue = []; + } +} From e04551d38a0a8b2c2bf9d675ba5c9d2460118cb4 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 26 Sep 2023 19:47:32 +0200 Subject: [PATCH 07/18] feat: block file deletion there is a folder being deleted --- .../controllers/DeleteController.ts | 11 ++++++++- .../modules/shared/domain/DelayQueue.ts | 24 +++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index 057351892..5d5a2c66f 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -6,6 +6,7 @@ import { DelayQueue } from 'workers/sync-engine/modules/shared/domain/DelayQueue export class DeleteController extends CallbackController { private readonly filesQueue: DelayQueue; + private readonly foldersQueue: Map; constructor( private readonly fileDeleter: FileDeleter, @@ -13,13 +14,19 @@ export class DeleteController extends CallbackController { ) { super(); + this.foldersQueue = new Map(); + const deleteAllFiles = async (files: Array) => { Logger.debug('Deleting queued files'); await Promise.all(files.map((f) => this.fileDeleter.run(f))); Logger.debug('Queued files deleted'); }; - this.filesQueue = new DelayQueue(deleteAllFiles); + const canDelete = () => { + return this.foldersQueue.size === 0; + }; + + this.filesQueue = new DelayQueue(deleteAllFiles, canDelete); } private async deleteFolder(uuid: string) { @@ -36,7 +43,9 @@ export class DeleteController extends CallbackController { return; } + this.foldersQueue.set(trimmedId); await this.deleteFolder(trimmedId); + this.foldersQueue.delete(trimmedId); return; } } diff --git a/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts index e5f8cc5e2..4e598b977 100644 --- a/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts +++ b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts @@ -1,22 +1,36 @@ +import Logger from 'electron-log'; + export class DelayQueue { private static readonly DELAY = 3_000; private queue: Array; private timeout: NodeJS.Timeout | null = null; constructor( - private readonly loopQueue: (queue: Array) => Promise + private readonly loopQueue: (queue: Array) => Promise, + private readonly canLoop: () => boolean ) { this.queue = []; } push(value: string) { - this.clearTimeout(); + this.setTimeout(); + + this.queue.push(value); + } + private setTimeout() { + this.clearTimeout(); this.timeout = setTimeout(async () => { - await this.loopQueue(this.queue); - }, DelayQueue.DELAY); + Logger.debug('WILL TRY TO RUN DELAY QUEUE'); + if (this.canLoop()) { + Logger.debug('RUNNING DELAY QUEUE'); + await this.loopQueue(this.queue); + return; + } - this.queue.push(value); + Logger.debug('LOOP BLOCKED'); + this.setTimeout(); + }, DelayQueue.DELAY); } private clearTimeout() { From 64c4e6c455a756214481b6e38f2d4ba57b650037 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 27 Sep 2023 11:39:00 +0200 Subject: [PATCH 08/18] fix: normalize path on http foler reposiotry --- .../modules/folders/infrastructure/HttpFolderRepository.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index 5a318558f..049687d99 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts @@ -12,6 +12,8 @@ import { UpdateFolderNameDTO } from './dtos/UpdateFolderNameDTO'; import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; import { RemoteItemsGenerator } from '../../items/application/RemoteItemsGenerator'; import { FolderStatuses } from '../domain/FolderStatus'; +import nodePath from 'path'; +import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathConverter'; export class HttpFolderRepository implements FolderRepository { public folders: Record = {}; @@ -108,8 +110,8 @@ export class HttpFolderRepository implements FolderRepository { status: FolderStatuses.EXISTS, }); - const p = folder.path.value.replace('\\', '/'); - + const c = nodePath.normalize(folder.path.value); + const p = PlatformPathConverter.winToPosix(c); this.folders[p] = folder; return folder; From 4efe0df2657d615716cbb8990d8e21637e2a9944 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 27 Sep 2023 11:40:18 +0200 Subject: [PATCH 09/18] feat: queue folder deletion --- .../controllers/DeleteController.ts | 42 +++++++++------- .../folders/application/FolderDeleter.ts | 3 ++ .../modules/shared/domain/DelayQueue.ts | 48 ++++++++++++------- 3 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index 5d5a2c66f..187961741 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -6,7 +6,7 @@ import { DelayQueue } from 'workers/sync-engine/modules/shared/domain/DelayQueue export class DeleteController extends CallbackController { private readonly filesQueue: DelayQueue; - private readonly foldersQueue: Map; + private readonly foldersQueue: DelayQueue; constructor( private readonly fileDeleter: FileDeleter, @@ -14,38 +14,46 @@ export class DeleteController extends CallbackController { ) { super(); - this.foldersQueue = new Map(); - - const deleteAllFiles = async (files: Array) => { + const deleteFile = async (file: string) => { Logger.debug('Deleting queued files'); - await Promise.all(files.map((f) => this.fileDeleter.run(f))); + this.fileDeleter.run(file); Logger.debug('Queued files deleted'); }; - const canDelete = () => { - return this.foldersQueue.size === 0; + const deleteFolder = async (folder: string) => { + Logger.debug('Deleting queued folders'); + this.folderDeleter.run(folder); + Logger.debug('Queued folders deleted'); }; - this.filesQueue = new DelayQueue(deleteAllFiles, canDelete); - } + const canDeleteFolders = () => { + // Folders can always be deleted + return true; + }; + + this.foldersQueue = new DelayQueue( + 'folders', + deleteFolder, + canDeleteFolders + ); + + const canDeleteFiles = () => { + // Files cannot be deleted if there are folders on the queue + return this.foldersQueue.size === 0; + }; - private async deleteFolder(uuid: string) { - Logger.debug('Deleting folder'); - await this.folderDeleter.run(uuid); - Logger.debug('Folder deleted'); + this.filesQueue = new DelayQueue('files', deleteFile, canDeleteFiles); } async execute(contentsId: string) { const trimmedId = this.trim(contentsId); + // TODO: need a better way to detect if its a file or a folder if (trimmedId.length === 24) { this.filesQueue.push(trimmedId); return; } - this.foldersQueue.set(trimmedId); - await this.deleteFolder(trimmedId); - this.foldersQueue.delete(trimmedId); - return; + this.foldersQueue.push(trimmedId); } } diff --git a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts index b203ea075..99cbf2b38 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts @@ -1,6 +1,7 @@ import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; +import Logger from 'electron-log'; export class FolderDeleter { constructor(private readonly repository: FolderRepository) {} @@ -14,5 +15,7 @@ export class FolderDeleter { folder.trash(); await this.repository.trash(folder); + + Logger.debug('Folder deleted: ', folder.name); } } diff --git a/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts index 4e598b977..3d5e65ecd 100644 --- a/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts +++ b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts @@ -2,46 +2,60 @@ import Logger from 'electron-log'; export class DelayQueue { private static readonly DELAY = 3_000; - private queue: Array; + private queue: Map; private timeout: NodeJS.Timeout | null = null; constructor( - private readonly loopQueue: (queue: Array) => Promise, + private readonly name: string, + private readonly fn: (item: string) => Promise, private readonly canLoop: () => boolean ) { - this.queue = []; + this.queue = new Map(); } - push(value: string) { - this.setTimeout(); - - this.queue.push(value); + private clearTimeout() { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } } private setTimeout() { this.clearTimeout(); this.timeout = setTimeout(async () => { - Logger.debug('WILL TRY TO RUN DELAY QUEUE'); + Logger.debug('Will try to run delay queue for: ', this.name); if (this.canLoop()) { - Logger.debug('RUNNING DELAY QUEUE'); - await this.loopQueue(this.queue); + Logger.debug('Running delay queue for: ', this.name); + + const reversedItems = Array.from(this.queue.keys()).reverse(); + + const promises = reversedItems.map(async (item) => { + await this.fn(item); + this.queue.delete(item); + }); + + await Promise.all(promises); + return; } - Logger.debug('LOOP BLOCKED'); + Logger.debug(this.name, ' loop blocked'); this.setTimeout(); }, DelayQueue.DELAY); } - private clearTimeout() { - if (this.timeout) { - clearTimeout(this.timeout); - this.timeout = null; - } + push(value: string) { + this.setTimeout(); + + this.queue.set(value); + } + + get size(): number { + return this.queue.size; } clear() { this.clearTimeout(); - this.queue = []; + this.queue.clear(); } } From 6051b9779cc1ff78770856066084722f00e343d1 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 27 Sep 2023 13:23:11 +0200 Subject: [PATCH 10/18] feat: delete only the deleted folder --- .../controllers/DeleteController.ts | 9 ++----- .../dependency-injection/files/builder.ts | 15 +++++------ .../folders/FoldersContainer.ts | 4 ++- .../dependency-injection/folders/builder.ts | 11 +++++++- .../modules/files/application/FileDeleter.ts | 24 +++++++++++------ .../folders/application/FolderDeleter.ts | 27 ++++++++++++++++--- .../ParentFoldersExistForDeletion.ts | 25 +++++++++++++++++ .../folders/application/WebdavFolderMover.ts | 4 +-- .../modules/folders/domain/Folder.ts | 2 +- .../modules/folders/domain/FolderStatus.ts | 4 +-- .../domain/errors/ActionNotPermitedError.ts | 5 ---- .../domain/errors/ActionNotPermittedError.ts | 5 ++++ .../infrastructure/HttpFolderRepository.ts | 25 ++++++++--------- .../modules/shared/domain/DelayQueue.ts | 8 +++--- 14 files changed, 112 insertions(+), 56 deletions(-) create mode 100644 src/workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion.ts delete mode 100644 src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermitedError.ts create mode 100644 src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermittedError.ts diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index 187961741..943034ecf 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -1,7 +1,6 @@ import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; import { FileDeleter } from '../../modules/files/application/FileDeleter'; import { CallbackController } from './CallbackController'; -import Logger from 'electron-log'; import { DelayQueue } from 'workers/sync-engine/modules/shared/domain/DelayQueue'; export class DeleteController extends CallbackController { @@ -15,15 +14,11 @@ export class DeleteController extends CallbackController { super(); const deleteFile = async (file: string) => { - Logger.debug('Deleting queued files'); - this.fileDeleter.run(file); - Logger.debug('Queued files deleted'); + await this.fileDeleter.run(file); }; const deleteFolder = async (folder: string) => { - Logger.debug('Deleting queued folders'); - this.folderDeleter.run(folder); - Logger.debug('Queued folders deleted'); + await this.folderDeleter.run(folder); }; const canDeleteFolders = () => { diff --git a/src/workers/sync-engine/dependency-injection/files/builder.ts b/src/workers/sync-engine/dependency-injection/files/builder.ts index c0df25a3a..82d8cbc5e 100644 --- a/src/workers/sync-engine/dependency-injection/files/builder.ts +++ b/src/workers/sync-engine/dependency-injection/files/builder.ts @@ -9,19 +9,17 @@ 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 { FolderFinder } from '../../modules/folders/application/FolderFinder'; import { NodeJsEventBus } from '../../modules/shared/infrastructure/DuplexEventBus'; import { DependencyInjectionHttpClientsProvider } from '../common/clients'; import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { DependencyInjectionTraverserProvider } from '../common/traverser'; import { DependencyInjectionUserProvider } from '../common/user'; +import { FoldersContainer } from '../folders/FoldersContainer'; import { FilesContainer } from './FilesContainer'; -export async function buildFilesContainer({ - folderFinder, -}: { - folderFinder: FolderFinder; -}): Promise { +export async function buildFilesContainer( + folderContainer: FoldersContainer +): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); const user = DependencyInjectionUserProvider.get(); @@ -48,6 +46,7 @@ export async function buildFilesContainer({ const fileDeleter = new FileDeleter( fileRepository, fileFinderByContentsId, + folderContainer.parentFoldersExistForDeletion, ipcRendererSyncEngine ); @@ -56,12 +55,12 @@ export async function buildFilesContainer({ const filePathUpdater = new FilePathUpdater( fileRepository, fileFinderByContentsId, - folderFinder + folderContainer.folderFinder ); const fileCreator = new FileCreator( fileRepository, - folderFinder, + folderContainer.folderFinder, new NodeJsEventBus() ); diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index ce403909d..0d30ee0f7 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -1,8 +1,9 @@ import { FolderCreator } from '../../modules/folders/application/FolderCreator'; +import { FolderDeleter } from '../../modules/folders/application/FolderDeleter'; import { FolderFinder } from '../../modules/folders/application/FolderFinder'; import { FolderPathFromAbsolutePathCreator } from '../../modules/folders/application/FolderPathFromAbsolutePathCreator'; import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; -import { FolderDeleter } from '../../modules/folders/application/FolderDeleter'; +import { ParentFoldersExistForDeletion } from '../../modules/folders/application/ParentFoldersExistForDeletion'; export interface FoldersContainer { folderCreator: FolderCreator; @@ -10,4 +11,5 @@ export interface FoldersContainer { folderPathFromAbsolutePathCreator: FolderPathFromAbsolutePathCreator; folderSearcher: FolderSearcher; folderDeleter: FolderDeleter; + parentFoldersExistForDeletion: ParentFoldersExistForDeletion; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 8453f5aa9..98df16f6b 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -9,6 +9,7 @@ import { FolderPathFromAbsolutePathCreator } from 'workers/sync-engine/modules/f import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; +import { ParentFoldersExistForDeletion } from 'workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion'; export async function buildFoldersContainer(): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); @@ -30,7 +31,14 @@ export async function buildFoldersContainer(): Promise { const folderSearcher = new FolderSearcher(repository); - const folderDeleter = new FolderDeleter(repository); + const parentFoldersExistForDeletion = new ParentFoldersExistForDeletion( + repository + ); + + const folderDeleter = new FolderDeleter( + repository, + parentFoldersExistForDeletion + ); const folderCreator = new FolderCreator( folderPathFromAbsolutePathCreator, @@ -45,5 +53,6 @@ export async function buildFoldersContainer(): Promise { folderPathFromAbsolutePathCreator, folderSearcher, folderDeleter, + parentFoldersExistForDeletion, }; } diff --git a/src/workers/sync-engine/modules/files/application/FileDeleter.ts b/src/workers/sync-engine/modules/files/application/FileDeleter.ts index b153b24d4..c3f6e6567 100644 --- a/src/workers/sync-engine/modules/files/application/FileDeleter.ts +++ b/src/workers/sync-engine/modules/files/application/FileDeleter.ts @@ -1,27 +1,37 @@ -import { FileRepository } from '../domain/FileRepository'; import Logger from 'electron-log'; -import { FileFinderByContentsId } from './FileFinderByContentsId'; -import { FileStatuses } from '../domain/FileStatus'; import { SyncEngineIpc } from '../../../ipcRendererSyncEngine'; +import { ParentFoldersExistForDeletion } from '../../folders/application/ParentFoldersExistForDeletion'; +import { FileRepository } from '../domain/FileRepository'; +import { FileStatuses } from '../domain/FileStatus'; +import { FileFinderByContentsId } from './FileFinderByContentsId'; export class FileDeleter { constructor( private readonly repository: FileRepository, private readonly fileFinder: FileFinderByContentsId, + private readonly parentFoldersExistForDeletion: ParentFoldersExistForDeletion, private readonly ipc: SyncEngineIpc ) {} async run(contentsId: string): Promise { const file = this.fileFinder.run(contentsId); - Logger.debug('FILE TO BE DELETED, ', file.nameWithExtension); - if (file.status.is(FileStatuses.TRASHED)) { - // TODO: Solve file deleter being called twice Logger.warn(`File ${file.path.value} is already trashed. Will ignore...`); return; } + const allParentsExists = this.parentFoldersExistForDeletion.run( + file.folderId + ); + + if (!allParentsExists) { + Logger.warn( + `Skipped file deletion for ${file.path.value}. A folder in a higher level is already marked as trashed` + ); + return; + } + this.ipc.send('DELETING_FILE', { name: file.name, extension: file.type, @@ -33,8 +43,6 @@ export class FileDeleter { await this.repository.delete(file); - Logger.debug('FILE DELETED, ', file.nameWithExtension); - this.ipc.send('FILE_DELETED', { name: file.name, extension: file.type, diff --git a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts index 99cbf2b38..ab46820da 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts @@ -1,10 +1,15 @@ +import Logger from 'electron-log'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; +import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; -import Logger from 'electron-log'; +import { ParentFoldersExistForDeletion } from './ParentFoldersExistForDeletion'; export class FolderDeleter { - constructor(private readonly repository: FolderRepository) {} + constructor( + private readonly repository: FolderRepository, + private readonly parentFoldersExistForDeletion: ParentFoldersExistForDeletion + ) {} async run(uuid: Folder['uuid']): Promise { const folder = this.repository.searchByPartial({ uuid }); @@ -13,9 +18,23 @@ export class FolderDeleter { throw new FolderNotFoundError(uuid); } + if (!folder.parentId) { + throw new ActionNotPermittedError('Trash root folder'); + } + + const allParentsExists = this.parentFoldersExistForDeletion.run( + // TODO: Create a new aggregate root for root folder so the rest have the parent Id as number + folder.parentId as number + ); + + if (!allParentsExists) { + Logger.warn( + `Skipped folder deletion for ${folder.path.value}. A folder in a higher level is already marked as trashed` + ); + return; + } + folder.trash(); await this.repository.trash(folder); - - Logger.debug('Folder deleted: ', folder.name); } } diff --git a/src/workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion.ts b/src/workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion.ts new file mode 100644 index 000000000..2ef02a579 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion.ts @@ -0,0 +1,25 @@ +import { Folder } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; +import { FolderStatuses } from '../domain/FolderStatus'; + +export class ParentFoldersExistForDeletion { + constructor(private readonly repository: FolderRepository) {} + + run(id: Folder['id']): boolean { + const folder = this.repository.searchByPartial({ id }); + + if (!folder) { + throw new Error(`Folder with id ${id} was not found`); + } + + if (!folder.hasStatus(FolderStatuses.EXISTS)) { + return false; + } + + if (!folder.parentId) { + return true; + } + + return this.run(folder.parentId); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts b/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts index eb87946f3..bea6b4a2c 100644 --- a/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts +++ b/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts @@ -1,4 +1,4 @@ -import { ActionNotPermitedError } from '../domain/errors/ActionNotPermitedError'; +import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; import { FolderPath } from '../domain/FolderPath'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; @@ -25,7 +25,7 @@ export class WebdavFolderMover { const shouldBeMerge = resultFolder !== undefined; if (shouldBeMerge) { - throw new ActionNotPermitedError('overwrite'); + throw new ActionNotPermittedError('overwrite'); } const destinationFolder = this.folderFinder.run(destination.dirname()); diff --git a/src/workers/sync-engine/modules/folders/domain/Folder.ts b/src/workers/sync-engine/modules/folders/domain/Folder.ts index 2668705e2..703214f09 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._status = this._status.changeTo(FolderStatuses.TRASHED); this.updatedAt = new Date(); - // TODO: recored trashed event + // TODO: record trashed event } isIn(folder: Folder): boolean { diff --git a/src/workers/sync-engine/modules/folders/domain/FolderStatus.ts b/src/workers/sync-engine/modules/folders/domain/FolderStatus.ts index d3901d908..567d13aea 100644 --- a/src/workers/sync-engine/modules/folders/domain/FolderStatus.ts +++ b/src/workers/sync-engine/modules/folders/domain/FolderStatus.ts @@ -1,6 +1,6 @@ import { InvalidArgumentError } from '../../../../shared/domain/InvalidArgumentError'; import { EnumValueObject } from '../../../../shared/domain/EnumValueObject'; -import { ActionNotPermitedError } from './errors/ActionNotPermitedError'; +import { ActionNotPermittedError } from './errors/ActionNotPermittedError'; export enum FolderStatuses { EXISTS = 'EXISTS', @@ -28,7 +28,7 @@ export class FolderStatus extends EnumValueObject { changeTo(status: FolderStatuses): FolderStatus { if (this.value === 'TRASHED') { - throw new ActionNotPermitedError('restore from trash'); + throw new ActionNotPermittedError('restore from trash'); } return new FolderStatus(FolderStatuses[status]); diff --git a/src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermitedError.ts b/src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermitedError.ts deleted file mode 100644 index fff78c4d3..000000000 --- a/src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermitedError.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class ActionNotPermitedError extends Error { - constructor(action: string) { - super(`${action} is not permited on folders`); - } -} diff --git a/src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermittedError.ts b/src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermittedError.ts new file mode 100644 index 000000000..aed1f9ece --- /dev/null +++ b/src/workers/sync-engine/modules/folders/domain/errors/ActionNotPermittedError.ts @@ -0,0 +1,5 @@ +export class ActionNotPermittedError extends Error { + constructor(action: string) { + super(`${action} is not permitted on folders`); + } +} diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index 049687d99..d9a8724df 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]; } @@ -110,9 +110,9 @@ export class HttpFolderRepository implements FolderRepository { status: FolderStatuses.EXISTS, }); - const c = nodePath.normalize(folder.path.value); - const p = PlatformPathConverter.winToPosix(c); - this.folders[p] = folder; + const normalized = nodePath.normalize(folder.path.value); + const posix = PlatformPathConverter.winToPosix(normalized); + this.folders[posix] = folder; return folder; } @@ -161,16 +161,17 @@ export class HttpFolderRepository implements FolderRepository { } ); - if (result.status === 200) { + if (result.status !== 200) { + Logger.error( + '[FOLDER REPOSITORY] Folder deletion failed with status: ', + result.status, + result.statusText + ); return; } - Logger.error( - '[FOLDER REPOSITORY] Folder deletion failed with status: ', - result.status, - result.statusText - ); - - await this.ipc.invoke('START_REMOTE_SYNC'); + const normalized = nodePath.normalize(folder.path.value); + const posix = PlatformPathConverter.winToPosix(normalized); + this.folders[posix] = folder; } } diff --git a/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts index 3d5e65ecd..121c8ea77 100644 --- a/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts +++ b/src/workers/sync-engine/modules/shared/domain/DelayQueue.ts @@ -27,14 +27,12 @@ export class DelayQueue { if (this.canLoop()) { Logger.debug('Running delay queue for: ', this.name); - const reversedItems = Array.from(this.queue.keys()).reverse(); + const reversedItems = Array.from(this.queue.entries()).reverse(); - const promises = reversedItems.map(async (item) => { + for (const [item] of reversedItems) { await this.fn(item); this.queue.delete(item); - }); - - await Promise.all(promises); + } return; } From e06cde710546b494c5722b4dc8b7f7988073f1ce Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 27 Sep 2023 16:35:03 +0200 Subject: [PATCH 11/18] feat: recreate file placeholder on delete error --- src/workers/sync-engine/BindingManager.ts | 12 +++--- .../DependencyContainerFactory.ts | 23 ++++++---- .../dependency-injection/common/eventBus.ts | 15 +++++++ .../files/FilesContainer.ts | 6 +++ .../dependency-injection/files/builder.ts | 42 ++++++++++++++++--- src/workers/sync-engine/index.ts | 8 ++-- .../CreateFilePlaceholderEmitter.ts | 12 ++++++ .../CreateFilePlaceholderOnDeletionFailed.ts | 18 ++++++++ .../modules/files/application/FileCreator.ts | 4 +- .../modules/files/application/FileDeleter.ts | 37 ++++++++++++---- .../FilePlaceholderCreatorFromContentsId.ts | 16 +++++++ .../events/OptimisticFileDeletionFailed.ts | 18 ++++++++ .../infrastructure/FilePlaceholderCreator.ts | 10 +++++ .../shared/domain/WebdavServerEventBus.ts | 2 +- .../infrastructure/DomainEventSubscribers.ts | 2 +- .../{DuplexEventBus.ts => NodeJsEventBus.ts} | 7 +--- .../shared/test/__mock__/EventBusMock.ts | 4 +- 17 files changed, 192 insertions(+), 44 deletions(-) create mode 100644 src/workers/sync-engine/dependency-injection/common/eventBus.ts create mode 100644 src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts create mode 100644 src/workers/sync-engine/modules/files/application/CreateFilePlaceholderOnDeletionFailed.ts create mode 100644 src/workers/sync-engine/modules/files/application/FilePlaceholderCreatorFromContentsId.ts create mode 100644 src/workers/sync-engine/modules/files/domain/events/OptimisticFileDeletionFailed.ts create mode 100644 src/workers/sync-engine/modules/files/infrastructure/FilePlaceholderCreator.ts rename src/workers/sync-engine/modules/shared/infrastructure/{DuplexEventBus.ts => NodeJsEventBus.ts} (78%) diff --git a/src/workers/sync-engine/BindingManager.ts b/src/workers/sync-engine/BindingManager.ts index 846c0edde..de7e27589 100644 --- a/src/workers/sync-engine/BindingManager.ts +++ b/src/workers/sync-engine/BindingManager.ts @@ -16,22 +16,20 @@ export class BindingsManager { } ) {} - private createFolderPlaceholder(folder: Folder) { + public createFolderPlaceholder(folder: Folder) { // In order to create a folder placeholder it's path must en with / const folderPath = `${folder.path.value}/`; this.drive.createItemByPath(folderPath, folder.uuid); } + public createFilePlaceholder(file: File) { + this.drive.createItemByPath(file.path.value, file.contentsId, file.size); + } public createPlaceHolders(items: Array) { items.forEach((item) => { if (item.isFile()) { - this.drive.createItemByPath( - item.path.value, - item.contentsId, - item.size - ); - return; + return this.createFilePlaceholder(item); } this.createFolderPlaceholder(item); diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index 2ac1aaf6b..c515fa71f 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -5,13 +5,18 @@ import { buildContentsContainer } from './contents/builder'; import { buildItemsContainer } from './items/builder'; import { buildFilesContainer } from './files/builder'; import { buildFoldersContainer } from './folders/builder'; +import { VirtualDrive } from 'virtual-drive/dist'; +import { DependencyInjectionEventBus } from './common/eventBus'; +import { DomainEventSubscribers } from '../modules/shared/infrastructure/DomainEventSubscribers'; export class DependencyContainerFactory { private static _container: DependencyContainer | undefined; - static readonly subscriptors: Array = []; + static readonly subscribers: Array = [ + 'createFilePlaceholderOnDeletionFailed', + ]; - eventSubscriptors( + eventSubscribers( key: keyof DependencyContainer ): DependencyContainer[keyof DependencyContainer] | undefined { if (!DependencyContainerFactory._container) return undefined; @@ -19,11 +24,7 @@ export class DependencyContainerFactory { return DependencyContainerFactory._container[key]; } - public get containter() { - return DependencyContainerFactory._container; - } - - async build(): Promise { + async build(drive: VirtualDrive): Promise { if (DependencyContainerFactory._container !== undefined) { return DependencyContainerFactory._container; } @@ -35,10 +36,15 @@ export class DependencyContainerFactory { const clients = getClients(); + const { bus } = DependencyInjectionEventBus; + const itemsContainer = buildItemsContainer(); const contentsContainer = await buildContentsContainer(); const foldersContainer = await buildFoldersContainer(); - const filesContainer = await buildFilesContainer(foldersContainer); + const { container: filesContainer } = await buildFilesContainer( + foldersContainer, + drive + ); const container = { drive: clients.drive, @@ -50,6 +56,7 @@ export class DependencyContainerFactory { ...foldersContainer, }; + bus.addSubscribers(DomainEventSubscribers.from(container)); DependencyContainerFactory._container = container; return container; diff --git a/src/workers/sync-engine/dependency-injection/common/eventBus.ts b/src/workers/sync-engine/dependency-injection/common/eventBus.ts new file mode 100644 index 000000000..be4939af9 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/common/eventBus.ts @@ -0,0 +1,15 @@ +import { NodeJsEventBus } from 'workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus'; + +export class DependencyInjectionEventBus { + private static _bus: NodeJsEventBus; + + static get bus(): NodeJsEventBus { + if (DependencyInjectionEventBus._bus) { + return DependencyInjectionEventBus._bus; + } + + DependencyInjectionEventBus._bus = new NodeJsEventBus(); + + 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 e0115c7ef..54e105acd 100644 --- a/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts +++ b/src/workers/sync-engine/dependency-injection/files/FilesContainer.ts @@ -1,9 +1,12 @@ +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'; 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'; import { LocalRepositoryRepositoryRefresher } from '../../modules/files/application/LocalRepositoryRepositoryRefresher'; @@ -16,4 +19,7 @@ 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 82d8cbc5e..5e066ca26 100644 --- a/src/workers/sync-engine/dependency-injection/files/builder.ts +++ b/src/workers/sync-engine/dependency-injection/files/builder.ts @@ -1,3 +1,4 @@ +import { CreateFilePlaceholderEmitter } from 'workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter'; import crypt from '../../../utils/crypt'; import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; import { FileByPartialSearcher } from '../../modules/files/application/FileByPartialSearcher'; @@ -9,21 +10,30 @@ 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 { NodeJsEventBus } from '../../modules/shared/infrastructure/DuplexEventBus'; 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'; import { FilesContainer } from './FilesContainer'; +import { VirtualDrive } from 'virtual-drive/dist'; +import { FilePlaceholderCreatorFromContentsId } from 'workers/sync-engine/modules/files/application/FilePlaceholderCreatorFromContentsId'; +import { FilePlaceholderCreator } from 'workers/sync-engine/modules/files/infrastructure/FilePlaceholderCreator'; +import { CreateFilePlaceholderOnDeletionFailed } from 'workers/sync-engine/modules/files/application/CreateFilePlaceholderOnDeletionFailed'; export async function buildFilesContainer( - folderContainer: FoldersContainer -): Promise { + folderContainer: FoldersContainer, + virtualDrive: VirtualDrive +): Promise<{ + container: FilesContainer; + subscribers: any; +}> { const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); const user = DependencyInjectionUserProvider.get(); const localRootFolderPath = DependencyInjectionLocalRootFolderPath.get(); + const { bus: eventBus } = DependencyInjectionEventBus; const fileRepository = new HttpFileRepository( crypt, @@ -43,10 +53,13 @@ export async function buildFilesContainer( fileRepository ); + const placeholderCreator = new FilePlaceholderCreator(virtualDrive); + const fileDeleter = new FileDeleter( fileRepository, fileFinderByContentsId, folderContainer.parentFoldersExistForDeletion, + placeholderCreator, ipcRendererSyncEngine ); @@ -61,7 +74,7 @@ export async function buildFilesContainer( const fileCreator = new FileCreator( fileRepository, folderContainer.folderFinder, - new NodeJsEventBus() + eventBus ); const filePathFromAbsolutePathCreator = new FilePathFromAbsolutePathCreator( @@ -70,6 +83,21 @@ export async function buildFilesContainer( const fileSearcher = new FileSearcher(fileRepository); + const createFilePlaceholderEmitter = new CreateFilePlaceholderEmitter( + eventBus + ); + + const filePlaceholderCreatorFromContentsId = + new FilePlaceholderCreatorFromContentsId( + fileFinderByContentsId, + placeholderCreator + ); + + const createFilePlaceholderOnDeletionFailed = + new CreateFilePlaceholderOnDeletionFailed( + filePlaceholderCreatorFromContentsId + ); + const container: FilesContainer = { fileFinderByContentsId, localRepositoryRefresher: localRepositoryRefresher, @@ -79,7 +107,11 @@ export async function buildFilesContainer( fileCreator, filePathFromAbsolutePathCreator, fileSearcher, + createFilePlaceholderEmitter: createFilePlaceholderEmitter, + filePlaceholderCreatorFromContentsId: filePlaceholderCreatorFromContentsId, + createFilePlaceholderOnDeletionFailed: + createFilePlaceholderOnDeletionFailed, }; - return container; + return { container, subscribers: [] }; } diff --git a/src/workers/sync-engine/index.ts b/src/workers/sync-engine/index.ts index 43b0a1eb0..bb9b6aac4 100644 --- a/src/workers/sync-engine/index.ts +++ b/src/workers/sync-engine/index.ts @@ -24,17 +24,15 @@ async function setUp() { const virtualDrivePath = await ipcRenderer.invoke('get-virtual-drive-root'); - Logger.info( - '[SYNC ENGINE] Going to create root sync folder on: ', - virtualDrivePath - ); + Logger.info('[SYNC ENGINE] Going to use root folder: ', virtualDrivePath); await ensureTheFolderExist(virtualDrivePath); const virtualDrive = new VirtualDrive(virtualDrivePath); const factory = new DependencyContainerFactory(); - const container = await factory.build(); + + const container = await factory.build(virtualDrive); const controllers = buildControllers(container); diff --git a/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts b/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts new file mode 100644 index 000000000..f9453f865 --- /dev/null +++ b/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderEmitter.ts @@ -0,0 +1,12 @@ +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/files/application/CreateFilePlaceholderOnDeletionFailed.ts b/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderOnDeletionFailed.ts new file mode 100644 index 000000000..d434dee54 --- /dev/null +++ b/src/workers/sync-engine/modules/files/application/CreateFilePlaceholderOnDeletionFailed.ts @@ -0,0 +1,18 @@ +import { DomainEventClass } from '../../shared/domain/DomainEvent'; +import { WebdavDomainEventSubscriber } from '../../shared/domain/WebdavDomainEventSubscriber'; +import { OptimisticFileDeletionFailed } from '../domain/events/OptimisticFileDeletionFailed'; +import { FilePlaceholderCreatorFromContentsId } from './FilePlaceholderCreatorFromContentsId'; + +export class CreateFilePlaceholderOnDeletionFailed + implements WebdavDomainEventSubscriber +{ + constructor(private readonly creator: FilePlaceholderCreatorFromContentsId) {} + + subscribedTo(): DomainEventClass[] { + return [OptimisticFileDeletionFailed]; + } + + async on(domainEvent: OptimisticFileDeletionFailed): Promise { + this.creator.run(domainEvent.toPrimitives().contentsId); + } +} diff --git a/src/workers/sync-engine/modules/files/application/FileCreator.ts b/src/workers/sync-engine/modules/files/application/FileCreator.ts index ccdc12ff8..a771ebac5 100644 --- a/src/workers/sync-engine/modules/files/application/FileCreator.ts +++ b/src/workers/sync-engine/modules/files/application/FileCreator.ts @@ -3,14 +3,14 @@ import { FilePath } from '../domain/FilePath'; import { File } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; import { FileSize } from '../domain/FileSize'; -import { WebdavServerEventBus } from '../../shared/domain/WebdavServerEventBus'; +import { EventBus } from '../../shared/domain/WebdavServerEventBus'; import { RemoteFileContents } from '../../contents/domain/RemoteFileContents'; export class FileCreator { constructor( private readonly repository: FileRepository, private readonly folderFinder: FolderFinder, - private readonly eventBus: WebdavServerEventBus + private readonly eventBus: EventBus ) {} async run( diff --git a/src/workers/sync-engine/modules/files/application/FileDeleter.ts b/src/workers/sync-engine/modules/files/application/FileDeleter.ts index c3f6e6567..f5d09e6bc 100644 --- a/src/workers/sync-engine/modules/files/application/FileDeleter.ts +++ b/src/workers/sync-engine/modules/files/application/FileDeleter.ts @@ -4,12 +4,15 @@ import { ParentFoldersExistForDeletion } from '../../folders/application/ParentF import { FileRepository } from '../domain/FileRepository'; import { FileStatuses } from '../domain/FileStatus'; import { FileFinderByContentsId } from './FileFinderByContentsId'; +import { FilePlaceholderCreator } from '../infrastructure/FilePlaceholderCreator'; export class FileDeleter { constructor( private readonly repository: FileRepository, private readonly fileFinder: FileFinderByContentsId, private readonly parentFoldersExistForDeletion: ParentFoldersExistForDeletion, + // TODO: don't import it directly from infrastructure + private readonly filePlaceholderCreator: FilePlaceholderCreator, private readonly ipc: SyncEngineIpc ) {} @@ -39,15 +42,33 @@ export class FileDeleter { size: file.size, }); - file.trash(); + try { + file.trash(); - await this.repository.delete(file); + await this.repository.delete(file); - this.ipc.send('FILE_DELETED', { - name: file.name, - extension: file.type, - nameWithExtension: file.nameWithExtension, - size: file.size, - }); + this.ipc.send('FILE_DELETED', { + name: file.name, + extension: file.type, + nameWithExtension: file.nameWithExtension, + size: file.size, + }); + } catch (error: unknown) { + Logger.error( + `Error deleting the file ${file.nameWithExtension}: `, + error + ); + + const message = error instanceof Error ? error.message : 'Unknown error'; + + this.ipc.send('FILE_DELETION_ERROR', { + name: file.name, + extension: file.type, + nameWithExtension: file.nameWithExtension, + error: message, + }); + + this.filePlaceholderCreator.run(file); + } } } diff --git a/src/workers/sync-engine/modules/files/application/FilePlaceholderCreatorFromContentsId.ts b/src/workers/sync-engine/modules/files/application/FilePlaceholderCreatorFromContentsId.ts new file mode 100644 index 000000000..a59762f74 --- /dev/null +++ b/src/workers/sync-engine/modules/files/application/FilePlaceholderCreatorFromContentsId.ts @@ -0,0 +1,16 @@ +import { File } from '../domain/File'; +import { FilePlaceholderCreator } from '../infrastructure/FilePlaceholderCreator'; +import { FileFinderByContentsId } from './FileFinderByContentsId'; + +export class FilePlaceholderCreatorFromContentsId { + constructor( + private readonly finder: FileFinderByContentsId, + private readonly placeholderCreator: FilePlaceholderCreator + ) {} + + run(contentsId: File['contentsId']) { + const file = this.finder.run(contentsId); + + this.placeholderCreator.run(file); + } +} diff --git a/src/workers/sync-engine/modules/files/domain/events/OptimisticFileDeletionFailed.ts b/src/workers/sync-engine/modules/files/domain/events/OptimisticFileDeletionFailed.ts new file mode 100644 index 000000000..41ccd25f4 --- /dev/null +++ b/src/workers/sync-engine/modules/files/domain/events/OptimisticFileDeletionFailed.ts @@ -0,0 +1,18 @@ +import { DomainEvent } from '../../../../modules/shared/domain/DomainEvent'; + +export class OptimisticFileDeletionFailed extends DomainEvent { + static readonly EVENT_NAME = 'file.deletion.failed'; + + constructor({ aggregateId }: { aggregateId: string }) { + super({ + eventName: OptimisticFileDeletionFailed.EVENT_NAME, + aggregateId, + }); + } + + toPrimitives() { + return { + contentsId: this.aggregateId, + }; + } +} diff --git a/src/workers/sync-engine/modules/files/infrastructure/FilePlaceholderCreator.ts b/src/workers/sync-engine/modules/files/infrastructure/FilePlaceholderCreator.ts new file mode 100644 index 000000000..66712ed08 --- /dev/null +++ b/src/workers/sync-engine/modules/files/infrastructure/FilePlaceholderCreator.ts @@ -0,0 +1,10 @@ +import { VirtualDrive } from 'virtual-drive/dist'; +import { File } from '../domain/File'; + +export class FilePlaceholderCreator { + constructor(private readonly drive: VirtualDrive) {} + + run(file: File): void { + this.drive.createItemByPath(file.path.value, file.contentsId, file.size); + } +} diff --git a/src/workers/sync-engine/modules/shared/domain/WebdavServerEventBus.ts b/src/workers/sync-engine/modules/shared/domain/WebdavServerEventBus.ts index af0aeaa2b..f427122ac 100644 --- a/src/workers/sync-engine/modules/shared/domain/WebdavServerEventBus.ts +++ b/src/workers/sync-engine/modules/shared/domain/WebdavServerEventBus.ts @@ -1,7 +1,7 @@ import { DomainEventSubscribers } from '../infrastructure/DomainEventSubscribers'; import { DomainEvent } from './DomainEvent'; -export interface WebdavServerEventBus { +export interface EventBus { publish(events: Array): Promise; addSubscribers(subscribers: DomainEventSubscribers): void; } diff --git a/src/workers/sync-engine/modules/shared/infrastructure/DomainEventSubscribers.ts b/src/workers/sync-engine/modules/shared/infrastructure/DomainEventSubscribers.ts index 5f2f9126d..b309f7621 100644 --- a/src/workers/sync-engine/modules/shared/infrastructure/DomainEventSubscribers.ts +++ b/src/workers/sync-engine/modules/shared/infrastructure/DomainEventSubscribers.ts @@ -7,7 +7,7 @@ export class DomainEventSubscribers { constructor(public items: Array>) {} static from(container: DependencyContainer): DomainEventSubscribers { - const subscribers = DependencyContainerFactory.subscriptors.map( + const subscribers = DependencyContainerFactory.subscribers.map( (subscriber) => { return container[subscriber]; } diff --git a/src/workers/sync-engine/modules/shared/infrastructure/DuplexEventBus.ts b/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts similarity index 78% rename from src/workers/sync-engine/modules/shared/infrastructure/DuplexEventBus.ts rename to src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts index 35e31442f..6ce84b9c8 100644 --- a/src/workers/sync-engine/modules/shared/infrastructure/DuplexEventBus.ts +++ b/src/workers/sync-engine/modules/shared/infrastructure/NodeJsEventBus.ts @@ -1,12 +1,9 @@ import EventEmitter from 'events'; import { DomainEvent } from '../domain/DomainEvent'; -import { WebdavServerEventBus } from '../domain/WebdavServerEventBus'; +import { EventBus } from '../domain/WebdavServerEventBus'; import { DomainEventSubscribers } from './DomainEventSubscribers'; -export class NodeJsEventBus - extends EventEmitter - implements WebdavServerEventBus -{ +export class NodeJsEventBus extends EventEmitter implements EventBus { async publish(events: Array): Promise { events.forEach((event) => { this.emit(event.eventName, event); 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 4f3e32bb4..89212bb69 100644 --- a/src/workers/sync-engine/modules/shared/test/__mock__/EventBusMock.ts +++ b/src/workers/sync-engine/modules/shared/test/__mock__/EventBusMock.ts @@ -1,8 +1,8 @@ import { DomainEvent } from '../../domain/DomainEvent'; -import { WebdavServerEventBus } from '../../domain/WebdavServerEventBus'; +import { EventBus } from '../../domain/WebdavServerEventBus'; import { DomainEventSubscribers } from '../../infrastructure/DomainEventSubscribers'; -export class EventBusMock implements WebdavServerEventBus { +export class EventBusMock implements EventBus { public publishMock = jest.fn(); async publish(events: DomainEvent[]) { From 3125cc1741602256feb6c7afa58ce71cb2473f22 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 27 Sep 2023 17:21:42 +0200 Subject: [PATCH 12/18] feat: create folder plachedor when deletion fails --- src/workers/sync-engine/BindingManager.ts | 32 +++++++++------ .../DependencyContainer.ts | 11 +++-- .../DependencyContainerFactory.ts | 16 ++++---- .../common/virtualDrive.ts | 20 +++++++++ .../dependency-injection/files/builder.ts | 6 +-- .../folders/FoldersContainer.ts | 2 + .../dependency-injection/folders/builder.ts | 23 +++++++---- src/workers/sync-engine/index.ts | 12 +----- .../folders/application/FolderDeleter.ts | 41 +++++++++++-------- .../FolderPlaceholderCreator.ts | 12 ++++++ 10 files changed, 114 insertions(+), 61 deletions(-) create mode 100644 src/workers/sync-engine/dependency-injection/common/virtualDrive.ts create mode 100644 src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts diff --git a/src/workers/sync-engine/BindingManager.ts b/src/workers/sync-engine/BindingManager.ts index de7e27589..818383110 100644 --- a/src/workers/sync-engine/BindingManager.ts +++ b/src/workers/sync-engine/BindingManager.ts @@ -1,15 +1,14 @@ -import { VirtualDrive } from 'virtual-drive'; import Logger from 'electron-log'; import { Folder } from './modules/folders/domain/Folder'; import { File } from './modules/files/domain/File'; +import { DependencyContainer } from './dependency-injection/DependencyContainer'; import { buildControllers } from './callbacks-controllers/buildControllers'; export class BindingsManager { private static readonly PROVIDER_NAME = 'Internxt'; constructor( - private readonly drive: VirtualDrive, - private readonly controllers: ReturnType, + private readonly container: DependencyContainer, private readonly paths: { root: string; icon: string; @@ -20,10 +19,14 @@ export class BindingsManager { // In order to create a folder placeholder it's path must en with / const folderPath = `${folder.path.value}/`; - this.drive.createItemByPath(folderPath, folder.uuid); + this.container.virtualDrive.createItemByPath(folderPath, folder.uuid); } public createFilePlaceholder(file: File) { - this.drive.createItemByPath(file.path.value, file.contentsId, file.size); + this.container.virtualDrive.createItemByPath( + file.path.value, + file.contentsId, + file.size + ); } public createPlaceHolders(items: Array) { @@ -38,12 +41,15 @@ export class BindingsManager { async start(version: string, providerId: string) { await this.stop(); + + const controllers = buildControllers(this.container); + const callbacks = { notifyDeleteCallback: ( contentsId: string, callback: (response: boolean) => void ) => { - this.controllers.delete + controllers.delete .execute(contentsId) .then(() => { Logger.debug('DELETE RESPONSE SUCCESSFUL'); @@ -63,7 +69,7 @@ export class BindingsManager { contentsId: string, callback: (response: boolean) => void ) => { - this.controllers.renameOrMoveFile.execute( + controllers.renameOrMoveFile.execute( absolutePath, contentsId, callback @@ -73,13 +79,13 @@ export class BindingsManager { absolutePath: string, callback: (acknowledge: boolean, id: string) => void ) => { - this.controllers.addFile.execute(absolutePath, callback); + controllers.addFile.execute(absolutePath, callback); }, fetchDataCallback: ( contentsId: string, callback: (success: boolean, path: string) => void ) => { - this.controllers.downloadFile + controllers.downloadFile .execute(contentsId) .then((path: string) => { callback(true, path); @@ -121,7 +127,7 @@ export class BindingsManager { }, }; - await this.drive.registerSyncRoot( + await this.container.virtualDrive.registerSyncRoot( BindingsManager.PROVIDER_NAME, version, providerId, @@ -129,14 +135,14 @@ export class BindingsManager { this.paths.icon ); - await this.drive.connectSyncRoot(); + await this.container.virtualDrive.connectSyncRoot(); } watch() { - this.drive.watchAndWait(this.paths.root); + this.container.virtualDrive.watchAndWait(this.paths.root); } async stop() { - await this.drive.disconnectSyncRoot(); + await this.container.virtualDrive.disconnectSyncRoot(); } } diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainer.ts b/src/workers/sync-engine/dependency-injection/DependencyContainer.ts index 52e428702..bb0b364d7 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainer.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainer.ts @@ -1,13 +1,14 @@ /* eslint-disable max-len */ -import { FileSearcher } from '../modules/files/application/FileSearcher'; +import { VirtualDrive } from 'virtual-drive/dist'; +import { FileCreator } from '../modules/files/application/FileCreator'; +import { FilePathFromAbsolutePathCreator } from '../modules/files/application/FilePathFromAbsolutePathCreator'; import { FilePathUpdater } from '../modules/files/application/FilePathUpdater'; +import { FileSearcher } from '../modules/files/application/FileSearcher'; import { FolderSearcher } from '../modules/folders/application/FolderSearcher'; -import { FilePathFromAbsolutePathCreator } from '../modules/files/application/FilePathFromAbsolutePathCreator'; -import { ItemsContainer } from './items/ItemsContainer'; import { ContentsContainer } from './contents/ContentsContainer'; -import { FileCreator } from '../modules/files/application/FileCreator'; import { FilesContainer } from './files/FilesContainer'; import { FoldersContainer } from './folders/FoldersContainer'; +import { ItemsContainer } from './items/ItemsContainer'; export interface DependencyContainer extends ItemsContainer, @@ -20,4 +21,6 @@ export interface DependencyContainer filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator; folderSearcher: FolderSearcher; + + virtualDrive: VirtualDrive; } diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index c515fa71f..c3d307994 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -1,13 +1,13 @@ import { getUser } from 'main/auth/service'; import { getClients } from '../../../shared/HttpClient/backgroud-process-clients'; +import { DomainEventSubscribers } from '../modules/shared/infrastructure/DomainEventSubscribers'; import { DependencyContainer } from './DependencyContainer'; +import { DependencyInjectionEventBus } from './common/eventBus'; import { buildContentsContainer } from './contents/builder'; -import { buildItemsContainer } from './items/builder'; import { buildFilesContainer } from './files/builder'; import { buildFoldersContainer } from './folders/builder'; -import { VirtualDrive } from 'virtual-drive/dist'; -import { DependencyInjectionEventBus } from './common/eventBus'; -import { DomainEventSubscribers } from '../modules/shared/infrastructure/DomainEventSubscribers'; +import { buildItemsContainer } from './items/builder'; +import { DependencyInjectionVirtualDrive } from './common/virtualDrive'; export class DependencyContainerFactory { private static _container: DependencyContainer | undefined; @@ -24,7 +24,7 @@ export class DependencyContainerFactory { return DependencyContainerFactory._container[key]; } - async build(drive: VirtualDrive): Promise { + async build(): Promise { if (DependencyContainerFactory._container !== undefined) { return DependencyContainerFactory._container; } @@ -37,13 +37,13 @@ export class DependencyContainerFactory { const clients = getClients(); const { bus } = DependencyInjectionEventBus; + const { virtualDrive } = DependencyInjectionVirtualDrive; const itemsContainer = buildItemsContainer(); const contentsContainer = await buildContentsContainer(); const foldersContainer = await buildFoldersContainer(); const { container: filesContainer } = await buildFilesContainer( - foldersContainer, - drive + foldersContainer ); const container = { @@ -54,6 +54,8 @@ export class DependencyContainerFactory { ...contentsContainer, ...filesContainer, ...foldersContainer, + + virtualDrive, }; bus.addSubscribers(DomainEventSubscribers.from(container)); diff --git a/src/workers/sync-engine/dependency-injection/common/virtualDrive.ts b/src/workers/sync-engine/dependency-injection/common/virtualDrive.ts new file mode 100644 index 000000000..e74b97645 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/common/virtualDrive.ts @@ -0,0 +1,20 @@ +import { VirtualDrive } from 'virtual-drive/dist'; +import { DependencyInjectionLocalRootFolderPath } from './localRootFolderPath'; + +export class DependencyInjectionVirtualDrive { + private static _vd: VirtualDrive; + + static get virtualDrive(): VirtualDrive { + if (DependencyInjectionVirtualDrive._vd) { + return DependencyInjectionVirtualDrive._vd; + } + + const root = DependencyInjectionLocalRootFolderPath.get(); + + const vd = new VirtualDrive(root); + + DependencyInjectionVirtualDrive._vd = vd; + + return DependencyInjectionVirtualDrive._vd; + } +} diff --git a/src/workers/sync-engine/dependency-injection/files/builder.ts b/src/workers/sync-engine/dependency-injection/files/builder.ts index 5e066ca26..f0b27c2a1 100644 --- a/src/workers/sync-engine/dependency-injection/files/builder.ts +++ b/src/workers/sync-engine/dependency-injection/files/builder.ts @@ -17,14 +17,13 @@ import { DependencyInjectionTraverserProvider } from '../common/traverser'; import { DependencyInjectionUserProvider } from '../common/user'; import { FoldersContainer } from '../folders/FoldersContainer'; import { FilesContainer } from './FilesContainer'; -import { VirtualDrive } from 'virtual-drive/dist'; import { FilePlaceholderCreatorFromContentsId } from 'workers/sync-engine/modules/files/application/FilePlaceholderCreatorFromContentsId'; import { FilePlaceholderCreator } from 'workers/sync-engine/modules/files/infrastructure/FilePlaceholderCreator'; import { CreateFilePlaceholderOnDeletionFailed } from 'workers/sync-engine/modules/files/application/CreateFilePlaceholderOnDeletionFailed'; +import { DependencyInjectionVirtualDrive } from '../common/virtualDrive'; export async function buildFilesContainer( - folderContainer: FoldersContainer, - virtualDrive: VirtualDrive + folderContainer: FoldersContainer ): Promise<{ container: FilesContainer; subscribers: any; @@ -34,6 +33,7 @@ export async function buildFilesContainer( const user = DependencyInjectionUserProvider.get(); const localRootFolderPath = DependencyInjectionLocalRootFolderPath.get(); const { bus: eventBus } = DependencyInjectionEventBus; + const { virtualDrive } = DependencyInjectionVirtualDrive; const fileRepository = new HttpFileRepository( crypt, diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index 0d30ee0f7..1378ef016 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -1,3 +1,4 @@ +import { FolderPlaceholderCreator } from '../../modules/folders/infrastructure/FolderPlaceholderCreator'; import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { FolderDeleter } from '../../modules/folders/application/FolderDeleter'; import { FolderFinder } from '../../modules/folders/application/FolderFinder'; @@ -12,4 +13,5 @@ export interface FoldersContainer { folderSearcher: FolderSearcher; folderDeleter: FolderDeleter; parentFoldersExistForDeletion: ParentFoldersExistForDeletion; + folderPlaceholderCreator: FolderPlaceholderCreator; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 98df16f6b..7a6b55fa2 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -1,20 +1,23 @@ import { FolderCreator } from 'workers/sync-engine/modules/folders/application/FolderCreator'; -import { FoldersContainer } from './FoldersContainer'; -import { HttpFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository'; -import { DependencyInjectionHttpClientsProvider } from '../common/clients'; -import { DependencyInjectionTraverserProvider } from '../common/traverser'; -import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; +import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; import { FolderFinder } from 'workers/sync-engine/modules/folders/application/FolderFinder'; import { FolderPathFromAbsolutePathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator'; -import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; -import { FolderDeleter } from 'workers/sync-engine/modules/folders/application/FolderDeleter'; import { ParentFoldersExistForDeletion } from 'workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion'; +import { FolderPlaceholderCreator } from 'workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator'; +import { HttpFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository'; +import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; +import { DependencyInjectionHttpClientsProvider } from '../common/clients'; +import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; +import { DependencyInjectionTraverserProvider } from '../common/traverser'; +import { FoldersContainer } from './FoldersContainer'; +import { DependencyInjectionVirtualDrive } from '../common/virtualDrive'; export async function buildFoldersContainer(): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); const rootFolderPath = DependencyInjectionLocalRootFolderPath.get(); + const { virtualDrive } = DependencyInjectionVirtualDrive; const repository = new HttpFolderRepository( clients.drive, @@ -35,9 +38,12 @@ export async function buildFoldersContainer(): Promise { repository ); + const folderPlaceholderCreator = new FolderPlaceholderCreator(virtualDrive); + const folderDeleter = new FolderDeleter( repository, - parentFoldersExistForDeletion + parentFoldersExistForDeletion, + folderPlaceholderCreator ); const folderCreator = new FolderCreator( @@ -54,5 +60,6 @@ export async function buildFoldersContainer(): Promise { folderSearcher, folderDeleter, parentFoldersExistForDeletion, + folderPlaceholderCreator, }; } diff --git a/src/workers/sync-engine/index.ts b/src/workers/sync-engine/index.ts index bb9b6aac4..5957490bc 100644 --- a/src/workers/sync-engine/index.ts +++ b/src/workers/sync-engine/index.ts @@ -19,24 +19,16 @@ async function ensureTheFolderExist(path: string) { async function setUp() { Logger.info('[SYNC ENGINE] Starting sync engine process'); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { VirtualDrive } = require('virtual-drive/dist'); - const virtualDrivePath = await ipcRenderer.invoke('get-virtual-drive-root'); Logger.info('[SYNC ENGINE] Going to use root folder: ', virtualDrivePath); await ensureTheFolderExist(virtualDrivePath); - const virtualDrive = new VirtualDrive(virtualDrivePath); - const factory = new DependencyContainerFactory(); + const container = await factory.build(); - const container = await factory.build(virtualDrive); - - const controllers = buildControllers(container); - - const bindings = new BindingsManager(virtualDrive, controllers, { + const bindings = new BindingsManager(container, { root: virtualDrivePath, icon: iconPath, }); diff --git a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts index ab46820da..06658167c 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts @@ -4,11 +4,14 @@ import { FolderRepository } from '../domain/FolderRepository'; import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; import { ParentFoldersExistForDeletion } from './ParentFoldersExistForDeletion'; +import { FolderPlaceholderCreator } from '../infrastructure/FolderPlaceholderCreator'; export class FolderDeleter { constructor( private readonly repository: FolderRepository, - private readonly parentFoldersExistForDeletion: ParentFoldersExistForDeletion + private readonly parentFoldersExistForDeletion: ParentFoldersExistForDeletion, + // TODO: do not import form infrastructure + private readonly folderPlaceholderCreator: FolderPlaceholderCreator ) {} async run(uuid: Folder['uuid']): Promise { @@ -18,23 +21,29 @@ export class FolderDeleter { throw new FolderNotFoundError(uuid); } - if (!folder.parentId) { - throw new ActionNotPermittedError('Trash root folder'); - } - - const allParentsExists = this.parentFoldersExistForDeletion.run( - // TODO: Create a new aggregate root for root folder so the rest have the parent Id as number - folder.parentId as number - ); + try { + if (!folder.parentId) { + throw new ActionNotPermittedError('Trash root folder'); + } - if (!allParentsExists) { - Logger.warn( - `Skipped folder deletion for ${folder.path.value}. A folder in a higher level is already marked as trashed` + const allParentsExists = this.parentFoldersExistForDeletion.run( + // TODO: Create a new aggregate root for root folder so the rest have the parent Id as number + folder.parentId as number ); - return; - } - folder.trash(); - await this.repository.trash(folder); + if (!allParentsExists) { + Logger.warn( + `Skipped folder deletion for ${folder.path.value}. A folder in a higher level is already marked as trashed` + ); + return; + } + + folder.trash(); + await this.repository.trash(folder); + } catch (error: unknown) { + Logger.error(`Error deleting the folder ${folder.name}: `, error); + + this.folderPlaceholderCreator.run(folder); + } } } diff --git a/src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts b/src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts new file mode 100644 index 000000000..6e03af84f --- /dev/null +++ b/src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts @@ -0,0 +1,12 @@ +import { VirtualDrive } from 'virtual-drive/dist'; +import { Folder } from '../domain/Folder'; + +export class FolderPlaceholderCreator { + constructor(private readonly drive: VirtualDrive) {} + + run(folder: Folder): void { + const folderPath = `${folder.path.value}/`; + + this.drive.createItemByPath(folderPath, folder.uuid); + } +} From d92bd018b3628f1c5afce924a51b8f2af6e9ab26 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 28 Sep 2023 12:11:20 +0200 Subject: [PATCH 13/18] feat: folder rename --- .../callbacks-controllers/buildControllers.ts | 1 + .../controllers/CallbackController.ts | 8 ++++ .../controllers/DeleteController.ts | 3 +- .../controllers/RenameOrMoveController.ts | 15 ++++-- .../folders/FoldersContainer.ts | 8 ++-- .../dependency-injection/folders/builder.ts | 21 +++++++-- .../application/ContentsDownloader.ts | 2 +- .../folders/application/FolderCreator.ts | 6 +-- .../{WebdavFolderMover.ts => FolderMover.ts} | 14 ++---- ...utePathCreator.ts => FolderPathCreator.ts} | 5 +- .../folders/application/FolderPathUpdater.ts | 46 +++++++++++++++++++ ...ebdavFolderRenamer.ts => FolderRenamer.ts} | 12 ++--- .../infrastructure/HttpFolderRepository.ts | 16 ++++++- .../test/__mocks__/FolderRepositoryMock.ts | 7 ++- ...olderMover.test.ts => FolderMover.test.ts} | 35 ++++---------- .../application/FolderPathCreator.test.ts | 8 ++-- 16 files changed, 137 insertions(+), 70 deletions(-) rename src/workers/sync-engine/modules/folders/application/{WebdavFolderMover.ts => FolderMover.ts} (67%) rename src/workers/sync-engine/modules/folders/application/{FolderPathFromAbsolutePathCreator.ts => FolderPathCreator.ts} (83%) create mode 100644 src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts rename src/workers/sync-engine/modules/folders/application/{WebdavFolderRenamer.ts => FolderRenamer.ts} (71%) rename src/workers/sync-engine/modules/folders/test/application/{WebdavFolderMover.test.ts => FolderMover.test.ts} (59%) diff --git a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts index 470ca76f4..06dea44d8 100644 --- a/src/workers/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/workers/sync-engine/callbacks-controllers/buildControllers.ts @@ -23,6 +23,7 @@ export function buildControllers(container: DependencyContainer) { const renameOrMoveFileController = new RenameOrMoveController( container.filePathFromAbsolutePathCreator, container.filePathUpdater, + container.folderPathUpdater, deleteController ); diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts index b30842f5d..2c382b8f6 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/CallbackController.ts @@ -6,4 +6,12 @@ export abstract class CallbackController { '' ); } + protected isContentsId(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; + } } diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts index 943034ecf..796c89cf1 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/DeleteController.ts @@ -43,8 +43,7 @@ export class DeleteController extends CallbackController { async execute(contentsId: string) { const trimmedId = this.trim(contentsId); - // TODO: need a better way to detect if its a file or a folder - if (trimmedId.length === 24) { + if (this.isContentsId(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 4f32a4ba8..19f4b873e 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/RenameOrMoveController.ts @@ -1,13 +1,15 @@ -import { FileDeleter } from 'workers/sync-engine/modules/files/application/FileDeleter'; +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'; export class RenameOrMoveController extends CallbackController { constructor( private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, private readonly filePathUpdater: FilePathUpdater, + private readonly folderPathUpdater: FolderPathUpdater, private readonly deleteController: DeleteController ) { super(); @@ -23,15 +25,20 @@ export class RenameOrMoveController extends CallbackController { try { if (absolutePath.startsWith('\\$Recycle.Bin')) { await this.deleteController.execute(trimmedId); - callback(true); - return; + return callback(true); } const relative = this.filePathFromAbsolutePathCreator.run(absolutePath); - await this.filePathUpdater.run(trimmedId, relative); + if (this.isContentsId(trimmedId)) { + await this.filePathUpdater.run(trimmedId, relative); + return callback(true); + } + + await this.folderPathUpdater.run(trimmedId, absolutePath); 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 1378ef016..abae403e9 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -1,17 +1,19 @@ -import { FolderPlaceholderCreator } from '../../modules/folders/infrastructure/FolderPlaceholderCreator'; import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { FolderDeleter } from '../../modules/folders/application/FolderDeleter'; import { FolderFinder } from '../../modules/folders/application/FolderFinder'; -import { FolderPathFromAbsolutePathCreator } from '../../modules/folders/application/FolderPathFromAbsolutePathCreator'; +import { FolderPathCreator } from '../../modules/folders/application/FolderPathCreator'; +import { FolderPathUpdater } from '../../modules/folders/application/FolderPathUpdater'; import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; import { ParentFoldersExistForDeletion } from '../../modules/folders/application/ParentFoldersExistForDeletion'; +import { FolderPlaceholderCreator } from '../../modules/folders/infrastructure/FolderPlaceholderCreator'; export interface FoldersContainer { folderCreator: FolderCreator; folderFinder: FolderFinder; - folderPathFromAbsolutePathCreator: FolderPathFromAbsolutePathCreator; + folderPathFromAbsolutePathCreator: FolderPathCreator; folderSearcher: FolderSearcher; folderDeleter: FolderDeleter; parentFoldersExistForDeletion: ParentFoldersExistForDeletion; folderPlaceholderCreator: FolderPlaceholderCreator; + folderPathUpdater: FolderPathUpdater; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 7a6b55fa2..95fae8fbe 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -1,7 +1,7 @@ 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 { FolderPathFromAbsolutePathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator'; +import { FolderPathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathCreator'; import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; import { ParentFoldersExistForDeletion } from 'workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion'; import { FolderPlaceholderCreator } from 'workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator'; @@ -12,6 +12,9 @@ import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolde import { DependencyInjectionTraverserProvider } from '../common/traverser'; import { FoldersContainer } from './FoldersContainer'; import { DependencyInjectionVirtualDrive } from '../common/virtualDrive'; +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'; export async function buildFoldersContainer(): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); @@ -27,8 +30,9 @@ export async function buildFoldersContainer(): Promise { ); await repository.init(); - const folderPathFromAbsolutePathCreator = - new FolderPathFromAbsolutePathCreator(rootFolderPath); + const folderPathFromAbsolutePathCreator = new FolderPathCreator( + rootFolderPath + ); const folderFinder = new FolderFinder(repository); @@ -53,6 +57,16 @@ export async function buildFoldersContainer(): Promise { ipcRendererSyncEngine ); + const folderMover = new FolderMover(repository, folderFinder); + const folderRenamer = new FolderRenamer(repository, ipcRendererSyncEngine); + + const folderPathUpdater = new FolderPathUpdater( + repository, + folderPathFromAbsolutePathCreator, + folderMover, + folderRenamer + ); + return { folderCreator, folderFinder, @@ -61,5 +75,6 @@ export async function buildFoldersContainer(): Promise { folderDeleter, parentFoldersExistForDeletion, folderPlaceholderCreator, + folderPathUpdater, }; } diff --git a/src/workers/sync-engine/modules/contents/application/ContentsDownloader.ts b/src/workers/sync-engine/modules/contents/application/ContentsDownloader.ts index 04c2cc27b..5c667a9ac 100644 --- a/src/workers/sync-engine/modules/contents/application/ContentsDownloader.ts +++ b/src/workers/sync-engine/modules/contents/application/ContentsDownloader.ts @@ -4,7 +4,7 @@ import { ContentFileDownloader } from '../domain/contentHandlers/ContentFileDown import { File } from '../../files/domain/File'; import { LocalFileContents } from '../domain/LocalFileContents'; import { LocalFileWriter } from '../domain/LocalFileWriter'; -import { Stopwatch } from 'shared/types/Stopwatch'; +import { Stopwatch } from '../../../../../shared/types/Stopwatch'; import Logger from 'electron-log'; export class ContentsDownloader { diff --git a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts index bc55e6793..5e7549097 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderCreator.ts @@ -3,18 +3,18 @@ import { PlatformPathConverter } from '../../shared/test/helpers/PlatformPathCon import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import { FolderFinder } from './FolderFinder'; -import { FolderPathFromAbsolutePathCreator } from './FolderPathFromAbsolutePathCreator'; +import { FolderPathCreator } from './FolderPathCreator'; export class FolderCreator { constructor( - private readonly folderPathFromAbsolutePathCreator: FolderPathFromAbsolutePathCreator, + 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.run( + const folderPath = this.folderPathFromAbsolutePathCreator.fromAbsolute( PlatformPathConverter.winToPosix(absolutePath) ); this.ipc.send('CREATING_FOLDER', { diff --git a/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts b/src/workers/sync-engine/modules/folders/application/FolderMover.ts similarity index 67% rename from src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts rename to src/workers/sync-engine/modules/folders/application/FolderMover.ts index bea6b4a2c..3791cfa41 100644 --- a/src/workers/sync-engine/modules/folders/application/WebdavFolderMover.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderMover.ts @@ -3,13 +3,11 @@ import { FolderPath } from '../domain/FolderPath'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import { FolderFinder } from './FolderFinder'; -import { WebdavFolderRenamer } from './WebdavFolderRenamer'; -export class WebdavFolderMover { +export class FolderMover { constructor( private readonly repository: FolderRepository, - private readonly folderFinder: FolderFinder, - private readonly folderRenamer: WebdavFolderRenamer + private readonly folderFinder: FolderFinder ) {} private async move(folder: Folder, parentFolder: Folder) { @@ -18,8 +16,7 @@ export class WebdavFolderMover { await this.repository.updateParentDir(folder); } - async run(folder: Folder, to: string): Promise { - const destination = new FolderPath(to); + async run(folder: Folder, destination: FolderPath): Promise { const resultFolder = this.repository.search(destination.value); const shouldBeMerge = resultFolder !== undefined; @@ -30,11 +27,6 @@ export class WebdavFolderMover { const destinationFolder = this.folderFinder.run(destination.dirname()); - if (folder.isIn(destinationFolder)) { - await this.folderRenamer.run(folder, to); - return; - } - await this.move(folder, destinationFolder); } } diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts b/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts similarity index 83% rename from src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts rename to src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts index 6241d2bfa..fca948223 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderPathFromAbsolutePathCreator.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderPathCreator.ts @@ -1,7 +1,7 @@ import path from 'path'; import { FolderPath } from '../domain/FolderPath'; -export class FolderPathFromAbsolutePathCreator { +export class FolderPathCreator { constructor(private readonly baseFolder: string) {} private calculateRelativePath(basePath: string, folderPath: string): string { @@ -12,7 +12,8 @@ export class FolderPathFromAbsolutePathCreator { return path.join(relativeFolders, fileName); } - run(absolutePath: string): FolderPath { + fromAbsolute(absolutePath: string): FolderPath { + // TODO: path.normalize can be better fit const sanitized = absolutePath.replace('\\\\', '\\'); const relative = this.calculateRelativePath(this.baseFolder, sanitized); diff --git a/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts new file mode 100644 index 000000000..a55d71986 --- /dev/null +++ b/src/workers/sync-engine/modules/folders/application/FolderPathUpdater.ts @@ -0,0 +1,46 @@ +import path from 'path'; +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'; + +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); + + const folder = this.repository.searchByPartial({ uuid }); + + if (!folder) { + throw new FolderNotFoundError(uuid); + } + + 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.folderMover.run(folder, desiredPath); + } + + if (nameChanged) { + return await this.folderRenamer.run(folder, desiredPath); + } + + throw new Error('No path change detected for folder path update'); + } +} diff --git a/src/workers/sync-engine/modules/folders/application/WebdavFolderRenamer.ts b/src/workers/sync-engine/modules/folders/application/FolderRenamer.ts similarity index 71% rename from src/workers/sync-engine/modules/folders/application/WebdavFolderRenamer.ts rename to src/workers/sync-engine/modules/folders/application/FolderRenamer.ts index ecb005093..e369e1e0a 100644 --- a/src/workers/sync-engine/modules/folders/application/WebdavFolderRenamer.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderRenamer.ts @@ -3,27 +3,25 @@ import { FolderPath } from '../domain/FolderPath'; import { Folder } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; -export class WebdavFolderRenamer { +export class FolderRenamer { constructor( private readonly repository: FolderRepository, private readonly ipc: SyncEngineIpc ) {} - async run(folder: Folder, destination: string) { - const path = new FolderPath(destination); - + async run(folder: Folder, destination: FolderPath) { this.ipc.send('RENAMING_FOLDER', { oldName: folder.name, - newName: path.name(), + newName: destination.name(), }); - folder.rename(path); + folder.rename(destination); await this.repository.updateName(folder); this.ipc.send('FOLDER_RENAMED', { oldName: folder.name, - newName: path.name(), + newName: destination.name(), }); } } diff --git a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts index d9a8724df..5824f79d0 100644 --- a/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts +++ b/src/workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository.ts @@ -132,6 +132,14 @@ export class HttpFolderRepository implements FolderRepository { `[REPOSITORY] Error updating item metadata: ${res.status}` ); } + + const old = this.searchByPartial({ uuid: folder.uuid }); + + if (old) { + delete this.folders[old?.path.value]; + } + + this.folders[folder.path.value] = folder; } async updateParentDir(folder: Folder): Promise { @@ -145,7 +153,13 @@ export class HttpFolderRepository implements FolderRepository { throw new Error(`[REPOSITORY] Error moving item: ${res.status}`); } - await this.init(); + const old = this.searchByPartial({ uuid: folder.uuid }); + + if (old) { + delete this.folders[old?.path.value]; + } + + this.folders[folder.path.value] = folder; } async searchOn(folder: Folder): Promise> { 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 d6deeedc3..7e241a78e 100644 --- a/src/workers/sync-engine/modules/folders/test/__mocks__/FolderRepositoryMock.ts +++ b/src/workers/sync-engine/modules/folders/test/__mocks__/FolderRepositoryMock.ts @@ -1,10 +1,11 @@ import { Nullable } from 'shared/types/Nullable'; import { FolderPath } from '../../domain/FolderPath'; -import { Folder } from '../../domain/Folder'; +import { Folder, FolderAttributes } from '../../domain/Folder'; import { FolderRepository } from '../../domain/FolderRepository'; export class FolderRepositoryMock implements FolderRepository { public mockSearch = jest.fn(); + public mockSearchByPartial = jest.fn(); public mockAdd = jest.fn(); public mockUpdateName = jest.fn(); public mockUpdateParentDir = jest.fn(); @@ -16,6 +17,10 @@ export class FolderRepositoryMock implements FolderRepository { return this.mockSearch(pathLike); } + searchByPartial(partial: Partial): Nullable { + return this.mockSearchByPartial(partial); + } + add(file: Folder): Promise { return this.mockAdd(file); } diff --git a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderMover.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderMover.test.ts similarity index 59% rename from src/workers/sync-engine/modules/folders/test/application/WebdavFolderMover.test.ts rename to src/workers/sync-engine/modules/folders/test/application/FolderMover.test.ts index e6b479cea..be0e98545 100644 --- a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderMover.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderMover.test.ts @@ -1,32 +1,30 @@ import { FolderFinder } from '../../application/FolderFinder'; -import { WebdavFolderMover } from '../../application/WebdavFolderMover'; -import { WebdavFolderRenamer } from '../../application/WebdavFolderRenamer'; +import { FolderMover } from '../../application/FolderMover'; import { FolderMother } from '../domain/FolderMother'; import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; import { IpcRendererSyncEngineMock } from '../../../shared/test/__mock__/IpcRendererSyncEngineMock'; +import { FolderPath } from '../../domain/FolderPath'; describe('Folder Mover', () => { let repository: FolderRepositoryMock; let folderFinder: FolderFinder; - let folderRenamer: WebdavFolderRenamer; let ipc: IpcRendererSyncEngineMock; - let SUT: WebdavFolderMover; + let SUT: FolderMover; beforeEach(() => { repository = new FolderRepositoryMock(); folderFinder = new FolderFinder(repository); ipc = new IpcRendererSyncEngineMock(); - folderRenamer = new WebdavFolderRenamer(repository, ipc); - SUT = new WebdavFolderMover(repository, folderFinder, folderRenamer); + SUT = new FolderMover(repository, folderFinder); }); - it('Folders cannot be ovewrited', async () => { + it('Folders cannot be overwrite', async () => { const folder = FolderMother.in(1, '/folderA/folderB'); - const destination = '/folderC/folderB'; + const destination = new FolderPath('/folderC/folderB'); repository.mockSearch.mockImplementation(() => - FolderMother.in(2, destination) + FolderMother.in(2, destination.value) ); try { @@ -43,7 +41,7 @@ describe('Folder Mover', () => { describe('Move', () => { it('moves a folder when the destination folder does not contain a folder with the same folder', async () => { const folder = FolderMother.in(1, '/folderA/folderB'); - const destination = '/folderC/folderB'; + const destination = new FolderPath('/folderC/folderB'); const folderC = FolderMother.in(2, '/folderC'); repository.mockSearch @@ -56,21 +54,4 @@ describe('Folder Mover', () => { expect(repository.mockUpdateName).not.toHaveBeenCalled(); }); }); - - describe('Rename', () => { - it('when a folder is moved to same folder its renamed', async () => { - const folderAId = 30010278; - const folder = FolderMother.in(folderAId, '/folderA/folderB'); - const destination = '/folderA/folderC'; - - repository.mockSearch - .mockReturnValueOnce(undefined) - .mockReturnValueOnce(FolderMother.withId(folderAId)); - - await SUT.run(folder, destination); - - expect(repository.mockUpdateName).toHaveBeenCalled(); - expect(repository.mockUpdateParentDir).not.toHaveBeenCalled(); - }); - }); }); 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 index 48edf9840..171e07a6e 100644 --- a/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts @@ -1,16 +1,14 @@ import path from 'path'; -import { FolderPathFromAbsolutePathCreator } from '../../application/FolderPathFromAbsolutePathCreator'; +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 FolderPathFromAbsolutePathCreator( - 'C\\:Users\\JWcer\\InternxtDrive' - ); + const sut = new FolderPathCreator('C\\:Users\\JWcer\\InternxtDrive'); - const result = sut.run(ab); + const result = sut.fromAbsolute(ab); expect(result.value).toBe('New folder (4)'); expect(path.dirname(result.value)).toBe('.'); From 13ce18398a612bde080a6bd7f6c01d3d6af3de34 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 28 Sep 2023 12:42:02 +0200 Subject: [PATCH 14/18] test: fix folder deleter --- .../folders/FoldersContainer.ts | 2 -- .../dependency-injection/folders/builder.ts | 13 +++++------- .../placeholders/PlaceholdersContainer.ts | 5 +++++ .../placeholders/builder.ts | 11 ++++++++++ .../folders/application/FolderDeleter.ts | 7 +++---- .../FolderPlaceholderCreator.ts | 12 ----------- ...rDeleter.test.ts => FolderDeleter.test.ts} | 20 ++++++++++++++++--- .../folders/test/domain/FolderMother.ts | 4 +++- .../placeholders/domain/PlaceholderCreator.ts | 7 +++++++ .../VirtualDrivePlaceholderCreator.ts | 18 +++++++++++++++++ .../test/__mock__/PlaceholderCreatorMock.ts | 16 +++++++++++++++ 11 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 src/workers/sync-engine/dependency-injection/placeholders/PlaceholdersContainer.ts create mode 100644 src/workers/sync-engine/dependency-injection/placeholders/builder.ts delete mode 100644 src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts rename src/workers/sync-engine/modules/folders/test/application/{WebdavFolderDeleter.test.ts => FolderDeleter.test.ts} (53%) create mode 100644 src/workers/sync-engine/modules/placeholders/domain/PlaceholderCreator.ts create mode 100644 src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts create mode 100644 src/workers/sync-engine/modules/placeholders/test/__mock__/PlaceholderCreatorMock.ts diff --git a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts index abae403e9..790f0c4f8 100644 --- a/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/workers/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -5,7 +5,6 @@ import { FolderPathCreator } from '../../modules/folders/application/FolderPathC import { FolderPathUpdater } from '../../modules/folders/application/FolderPathUpdater'; import { FolderSearcher } from '../../modules/folders/application/FolderSearcher'; import { ParentFoldersExistForDeletion } from '../../modules/folders/application/ParentFoldersExistForDeletion'; -import { FolderPlaceholderCreator } from '../../modules/folders/infrastructure/FolderPlaceholderCreator'; export interface FoldersContainer { folderCreator: FolderCreator; @@ -14,6 +13,5 @@ export interface FoldersContainer { folderSearcher: FolderSearcher; folderDeleter: FolderDeleter; parentFoldersExistForDeletion: ParentFoldersExistForDeletion; - folderPlaceholderCreator: FolderPlaceholderCreator; folderPathUpdater: FolderPathUpdater; } diff --git a/src/workers/sync-engine/dependency-injection/folders/builder.ts b/src/workers/sync-engine/dependency-injection/folders/builder.ts index 95fae8fbe..3af60d3ab 100644 --- a/src/workers/sync-engine/dependency-injection/folders/builder.ts +++ b/src/workers/sync-engine/dependency-injection/folders/builder.ts @@ -4,23 +4,23 @@ import { FolderFinder } from 'workers/sync-engine/modules/folders/application/Fo import { FolderPathCreator } from 'workers/sync-engine/modules/folders/application/FolderPathCreator'; import { FolderSearcher } from 'workers/sync-engine/modules/folders/application/FolderSearcher'; import { ParentFoldersExistForDeletion } from 'workers/sync-engine/modules/folders/application/ParentFoldersExistForDeletion'; -import { FolderPlaceholderCreator } from 'workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator'; import { HttpFolderRepository } from 'workers/sync-engine/modules/folders/infrastructure/HttpFolderRepository'; import { ipcRendererSyncEngine } from '../../ipcRendererSyncEngine'; import { DependencyInjectionHttpClientsProvider } from '../common/clients'; import { DependencyInjectionLocalRootFolderPath } from '../common/localRootFolderPath'; import { DependencyInjectionTraverserProvider } from '../common/traverser'; import { FoldersContainer } from './FoldersContainer'; -import { DependencyInjectionVirtualDrive } from '../common/virtualDrive'; 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'; -export async function buildFoldersContainer(): Promise { +export async function buildFoldersContainer( + placeholdersContainer: PlaceholderContainer +): Promise { const clients = DependencyInjectionHttpClientsProvider.get(); const traverser = DependencyInjectionTraverserProvider.get(); const rootFolderPath = DependencyInjectionLocalRootFolderPath.get(); - const { virtualDrive } = DependencyInjectionVirtualDrive; const repository = new HttpFolderRepository( clients.drive, @@ -42,12 +42,10 @@ export async function buildFoldersContainer(): Promise { repository ); - const folderPlaceholderCreator = new FolderPlaceholderCreator(virtualDrive); - const folderDeleter = new FolderDeleter( repository, parentFoldersExistForDeletion, - folderPlaceholderCreator + placeholdersContainer.placeholderCreator ); const folderCreator = new FolderCreator( @@ -74,7 +72,6 @@ export async function buildFoldersContainer(): Promise { folderSearcher, folderDeleter, parentFoldersExistForDeletion, - folderPlaceholderCreator, folderPathUpdater, }; } diff --git a/src/workers/sync-engine/dependency-injection/placeholders/PlaceholdersContainer.ts b/src/workers/sync-engine/dependency-injection/placeholders/PlaceholdersContainer.ts new file mode 100644 index 000000000..4409d8121 --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/placeholders/PlaceholdersContainer.ts @@ -0,0 +1,5 @@ +import { PlaceholderCreator } from '../../modules/placeholders/domain/PlaceholderCreator'; + +export interface PlaceholderContainer { + placeholderCreator: PlaceholderCreator; +} diff --git a/src/workers/sync-engine/dependency-injection/placeholders/builder.ts b/src/workers/sync-engine/dependency-injection/placeholders/builder.ts new file mode 100644 index 000000000..eb44e9aca --- /dev/null +++ b/src/workers/sync-engine/dependency-injection/placeholders/builder.ts @@ -0,0 +1,11 @@ +import { DependencyInjectionVirtualDrive } from '../common/virtualDrive'; +import { PlaceholderContainer } from './PlaceholdersContainer'; +import { VirtualDrivePlaceholderCreator } from 'workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator'; + +export function buildPlaceholdersContainer(): PlaceholderContainer { + const { virtualDrive } = DependencyInjectionVirtualDrive; + + const placeholderCreator = new VirtualDrivePlaceholderCreator(virtualDrive); + + return { placeholderCreator }; +} diff --git a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts index 06658167c..ff38c44de 100644 --- a/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts +++ b/src/workers/sync-engine/modules/folders/application/FolderDeleter.ts @@ -4,14 +4,13 @@ import { FolderRepository } from '../domain/FolderRepository'; import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; import { ParentFoldersExistForDeletion } from './ParentFoldersExistForDeletion'; -import { FolderPlaceholderCreator } from '../infrastructure/FolderPlaceholderCreator'; +import { PlaceholderCreator } from '../../placeholders/domain/PlaceholderCreator'; export class FolderDeleter { constructor( private readonly repository: FolderRepository, private readonly parentFoldersExistForDeletion: ParentFoldersExistForDeletion, - // TODO: do not import form infrastructure - private readonly folderPlaceholderCreator: FolderPlaceholderCreator + private readonly placeholderCreator: PlaceholderCreator ) {} async run(uuid: Folder['uuid']): Promise { @@ -43,7 +42,7 @@ export class FolderDeleter { } catch (error: unknown) { Logger.error(`Error deleting the folder ${folder.name}: `, error); - this.folderPlaceholderCreator.run(folder); + this.placeholderCreator.folder(folder); } } } diff --git a/src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts b/src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts deleted file mode 100644 index 6e03af84f..000000000 --- a/src/workers/sync-engine/modules/folders/infrastructure/FolderPlaceholderCreator.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { VirtualDrive } from 'virtual-drive/dist'; -import { Folder } from '../domain/Folder'; - -export class FolderPlaceholderCreator { - constructor(private readonly drive: VirtualDrive) {} - - run(folder: Folder): void { - const folderPath = `${folder.path.value}/`; - - this.drive.createItemByPath(folderPath, folder.uuid); - } -} diff --git a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderDeleter.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderDeleter.test.ts similarity index 53% rename from src/workers/sync-engine/modules/folders/test/application/WebdavFolderDeleter.test.ts rename to src/workers/sync-engine/modules/folders/test/application/FolderDeleter.test.ts index c6e6334b6..92b634add 100644 --- a/src/workers/sync-engine/modules/folders/test/application/WebdavFolderDeleter.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderDeleter.test.ts @@ -2,20 +2,34 @@ import { FolderDeleter } from '../../application/FolderDeleter'; import { FolderStatus } from '../../domain/FolderStatus'; import { FolderMother } from '../domain/FolderMother'; import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; +import { ParentFoldersExistForDeletion } from '../../application/ParentFoldersExistForDeletion'; +import { PlaceholderCreatorMock } from '../../../placeholders/test/__mock__/PlaceholderCreatorMock'; describe('Folder deleter', () => { let repository: FolderRepositoryMock; + let parentFoldersExistForDeletion: ParentFoldersExistForDeletion; let SUT: FolderDeleter; beforeEach(() => { repository = new FolderRepositoryMock(); - SUT = new FolderDeleter(repository); + parentFoldersExistForDeletion = new ParentFoldersExistForDeletion( + repository + ); + const placeholderCreator = new PlaceholderCreatorMock(); + SUT = new FolderDeleter( + repository, + parentFoldersExistForDeletion, + placeholderCreator + ); }); it('trashes an existing folder', () => { const folder = FolderMother.exists(); - SUT.run(folder); + repository.mockSearchByPartial.mockReturnValueOnce(folder); + jest.spyOn(parentFoldersExistForDeletion, 'run').mockReturnValueOnce(true); + + SUT.run(folder.uuid); expect(repository.mockTrash).toBeCalledWith( expect.objectContaining({ @@ -27,7 +41,7 @@ describe('Folder deleter', () => { it('throws an error when trashing a folder already trashed', () => { const folder = FolderMother.trashed(); - SUT.run(folder).catch((err) => { + SUT.run(folder.uuid).catch((err) => { expect(err).toBeDefined(); }); 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 c342a7019..18c642dfb 100644 --- a/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts +++ b/src/workers/sync-engine/modules/folders/test/domain/FolderMother.ts @@ -2,6 +2,8 @@ import { File } from '../../../files/domain/File'; import { FolderStatuses } from '../../domain/FolderStatus'; import { Folder } from '../../domain/Folder'; import { FolderUuid } from '../../domain/FolderUuid'; +import Chance from 'chance'; +const chance = new Chance(); export class FolderMother { static containing(file: File) { @@ -57,7 +59,7 @@ export class FolderMother { id: 2048, uuid: FolderUuid.random().value, path: '/Zodseve', - parentId: null, + parentId: chance.integer({ min: 1 }), updatedAt: new Date().toISOString(), createdAt: new Date().toISOString(), status: FolderStatuses.EXISTS, diff --git a/src/workers/sync-engine/modules/placeholders/domain/PlaceholderCreator.ts b/src/workers/sync-engine/modules/placeholders/domain/PlaceholderCreator.ts new file mode 100644 index 000000000..21102c3e7 --- /dev/null +++ b/src/workers/sync-engine/modules/placeholders/domain/PlaceholderCreator.ts @@ -0,0 +1,7 @@ +import { File } from '../../files/domain/File'; +import { Folder } from '../../folders/domain/Folder'; + +export interface PlaceholderCreator { + folder: (folder: Folder) => void; + file: (file: File) => void; +} diff --git a/src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts b/src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts new file mode 100644 index 000000000..18730153d --- /dev/null +++ b/src/workers/sync-engine/modules/placeholders/infrastructure/VirtualDrivePlaceholderCreator.ts @@ -0,0 +1,18 @@ +import { VirtualDrive } from 'virtual-drive/dist'; +import { File } from '../../files/domain/File'; +import { Folder } from '../../folders/domain/Folder'; +import { PlaceholderCreator } from '../domain/PlaceholderCreator'; + +export class VirtualDrivePlaceholderCreator implements PlaceholderCreator { + constructor(private readonly drive: VirtualDrive) {} + + folder(folder: Folder): void { + const folderPath = `${folder.path.value}/`; + + this.drive.createItemByPath(folderPath, folder.uuid); + } + + file(file: File): void { + this.drive.createItemByPath(file.path.value, file.contentsId, file.size); + } +} diff --git a/src/workers/sync-engine/modules/placeholders/test/__mock__/PlaceholderCreatorMock.ts b/src/workers/sync-engine/modules/placeholders/test/__mock__/PlaceholderCreatorMock.ts new file mode 100644 index 000000000..ac12fd92a --- /dev/null +++ b/src/workers/sync-engine/modules/placeholders/test/__mock__/PlaceholderCreatorMock.ts @@ -0,0 +1,16 @@ +import { File } from 'workers/sync-engine/modules/files/domain/File'; +import { PlaceholderCreator } from '../../domain/PlaceholderCreator'; +import { Folder } from 'workers/sync-engine/modules/folders/domain/Folder'; + +export class PlaceholderCreatorMock implements PlaceholderCreator { + fileMock = jest.fn(); + folderMock = jest.fn(); + + file(file: File) { + this.fileMock(file); + } + + folder(folder: Folder) { + this.folderMock(folder); + } +} From e68e4fd0327891259388fbc97cca4a9173686442 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 28 Sep 2023 12:50:29 +0200 Subject: [PATCH 15/18] test: folder deletion fallback --- .../test/application/FolderDeleter.test.ts | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/workers/sync-engine/modules/folders/test/application/FolderDeleter.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderDeleter.test.ts index 92b634add..4589ccd34 100644 --- a/src/workers/sync-engine/modules/folders/test/application/FolderDeleter.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderDeleter.test.ts @@ -7,6 +7,7 @@ import { PlaceholderCreatorMock } from '../../../placeholders/test/__mock__/Plac describe('Folder deleter', () => { let repository: FolderRepositoryMock; + let placeholderCreator: PlaceholderCreatorMock; let parentFoldersExistForDeletion: ParentFoldersExistForDeletion; let SUT: FolderDeleter; @@ -15,7 +16,7 @@ describe('Folder deleter', () => { parentFoldersExistForDeletion = new ParentFoldersExistForDeletion( repository ); - const placeholderCreator = new PlaceholderCreatorMock(); + placeholderCreator = new PlaceholderCreatorMock(); SUT = new FolderDeleter( repository, parentFoldersExistForDeletion, @@ -23,13 +24,13 @@ describe('Folder deleter', () => { ); }); - it('trashes an existing folder', () => { + it('trashes an existing folder', async () => { const folder = FolderMother.exists(); repository.mockSearchByPartial.mockReturnValueOnce(folder); jest.spyOn(parentFoldersExistForDeletion, 'run').mockReturnValueOnce(true); - SUT.run(folder.uuid); + await SUT.run(folder.uuid); expect(repository.mockTrash).toBeCalledWith( expect.objectContaining({ @@ -38,13 +39,43 @@ describe('Folder deleter', () => { ); }); - it('throws an error when trashing a folder already trashed', () => { + it('throws an error when trashing a folder already trashed', async () => { const folder = FolderMother.trashed(); - SUT.run(folder.uuid).catch((err) => { + repository.mockSearchByPartial.mockReturnValueOnce(folder); + jest.spyOn(parentFoldersExistForDeletion, 'run').mockReturnValueOnce(true); + + await SUT.run(folder.uuid).catch((err) => { + expect(err).toBeDefined(); + }); + + expect(repository.mockTrash).not.toBeCalled(); + }); + + it('does not delete the folder if a higher folder is already deleted ', async () => { + const folder = FolderMother.exists(); + + repository.mockSearchByPartial.mockReturnValueOnce(folder); + jest.spyOn(parentFoldersExistForDeletion, 'run').mockReturnValueOnce(false); + + await SUT.run(folder.uuid).catch((err) => { expect(err).toBeDefined(); }); expect(repository.mockTrash).not.toBeCalled(); }); + + it('recreates the placeholder if the deletion fails', async () => { + const folder = FolderMother.exists(); + + repository.mockSearchByPartial.mockReturnValueOnce(folder); + jest.spyOn(parentFoldersExistForDeletion, 'run').mockReturnValueOnce(true); + repository.mockTrash.mockRejectedValue( + new Error('Error during the deletion') + ); + + await SUT.run(folder.uuid); + + expect(placeholderCreator.folderMock).toBeCalledWith(folder); + }); }); From 03fdd21d3a540d212a789294f0fc969f6a1b3ceb Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 28 Sep 2023 13:02:32 +0200 Subject: [PATCH 16/18] test: fix http file repo test --- .../infrastructure/HttpFileRepository.test.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/workers/sync-engine/modules/files/test/infrastructure/HttpFileRepository.test.ts b/src/workers/sync-engine/modules/files/test/infrastructure/HttpFileRepository.test.ts index d482647e0..1a1be8894 100644 --- a/src/workers/sync-engine/modules/files/test/infrastructure/HttpFileRepository.test.ts +++ b/src/workers/sync-engine/modules/files/test/infrastructure/HttpFileRepository.test.ts @@ -41,7 +41,7 @@ describe('Http File Repository', () => { describe('Rename', () => { it('after a file is renamed cannot be found ', async () => { - const files = ['a', 'b', 'c', 'd'].map((char: string) => + const originalFiles = ['a', 'b', 'c', 'd'].map((char: string) => ServerFileMother.fromPartial({ name: char, folderId: rootFolderId, @@ -50,10 +50,24 @@ describe('Http File Repository', () => { }) ); - ipc.onInvokeMock.mockResolvedValueOnce({ - folders: [rootFolder], - files: files, - }); + const resultFiles = ['aa', 'b', 'c', 'd'].map((char: string) => + ServerFileMother.fromPartial({ + name: char, + folderId: rootFolderId, + fileId: chance.string({ length: 24 }), + type: '', + }) + ); + + ipc.onInvokeMock + .mockResolvedValueOnce({ + folders: [rootFolder], + files: originalFiles, + }) + .mockResolvedValueOnce({ + folders: [rootFolder], + files: resultFiles, + }); axios.post = jest.fn().mockResolvedValueOnce({ status: 200, data: {} }); From 718331627863518a5bb2fed3ab79d3a8f6f12917 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 28 Sep 2023 13:08:30 +0200 Subject: [PATCH 17/18] test: update folder path creator --- .../folders/test/application/FolderPathCreator.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 index 171e07a6e..31612c56d 100644 --- a/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderPathCreator.test.ts @@ -10,8 +10,9 @@ describe('Folder Phat Creator', () => { const result = sut.fromAbsolute(ab); - expect(result.value).toBe('New folder (4)'); - expect(path.dirname(result.value)).toBe('.'); + // 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); }); }); From 2a7861cac5da78bb5c2258874ee1cc78ccf585d6 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 28 Sep 2023 13:18:47 +0200 Subject: [PATCH 18/18] fix: ts fixes --- .../controllers/AddFileController.ts | 52 ++++++++----------- .../DependencyContainerFactory.ts | 4 +- src/workers/sync-engine/index.ts | 1 - .../test/application/FolderMover.test.ts | 3 -- .../modules/shared/domain/MapObserver.ts | 6 +-- 5 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts index a82d8c4bd..9d50d611a 100644 --- a/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts +++ b/src/workers/sync-engine/callbacks-controllers/controllers/AddFileController.ts @@ -11,22 +11,31 @@ import { FolderCreator } from '../../modules/folders/application/FolderCreator'; import { MapObserver } from 'workers/sync-engine/modules/shared/domain/MapObserver'; import { FolderFinder } from 'workers/sync-engine/modules/folders/application/FolderFinder'; -export type DehydrateAndCreatePlaceholder = ( - id: string, - relativePath: string, - size: number -) => void; +type Queue = Map void>; export class AddFileController extends CallbackController { - private filesQueue = new Map< - string, - (acknowledge: boolean, id: string) => void - >(); + private readonly filesQueue: Queue; + private readonly foldersQueue: Queue; - private foldersQueue = new Map< - string, - (acknowledge: boolean, id: string) => void - >(); + private readonly observer: MapObserver; + + constructor( + private readonly contentsUploader: RetryContentsUploader, + private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, + private readonly fileCreator: FileCreator, + private readonly fileDeleter: FileDeleter, + private readonly searchByPartial: FileByPartialSearcher, + private readonly folderCreator: FolderCreator, + private readonly folderFinder: FolderFinder + ) { + super(); + + this.filesQueue = new Map(); + this.foldersQueue = new Map(); + + this.observer = new MapObserver(this.foldersQueue, this.createFiles); + this.observer.startObserving(); + } private createFile = async ( absolutePath: string, @@ -90,23 +99,6 @@ export class AddFileController extends CallbackController { } }; - private readonly obsever = new MapObserver( - this.foldersQueue, - this.createFiles - ); - - constructor( - private readonly contentsUploader: RetryContentsUploader, - private readonly filePathFromAbsolutePathCreator: FilePathFromAbsolutePathCreator, - private readonly fileCreator: FileCreator, - private readonly fileDeleter: FileDeleter, - private readonly searchByPartial: FileByPartialSearcher, - private readonly folderCreator: FolderCreator, - private readonly folderFinder: FolderFinder - ) { - super(); - } - async execute( absolutePath: string, callback: (acknowledge: boolean, id: string) => void diff --git a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts index c3d307994..b8acb9206 100644 --- a/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts +++ b/src/workers/sync-engine/dependency-injection/DependencyContainerFactory.ts @@ -8,6 +8,7 @@ import { buildFilesContainer } from './files/builder'; import { buildFoldersContainer } from './folders/builder'; import { buildItemsContainer } from './items/builder'; import { DependencyInjectionVirtualDrive } from './common/virtualDrive'; +import { buildPlaceholdersContainer } from './placeholders/builder'; export class DependencyContainerFactory { private static _container: DependencyContainer | undefined; @@ -39,9 +40,10 @@ export class DependencyContainerFactory { const { bus } = DependencyInjectionEventBus; const { virtualDrive } = DependencyInjectionVirtualDrive; + const PlaceholderContainer = buildPlaceholdersContainer(); const itemsContainer = buildItemsContainer(); const contentsContainer = await buildContentsContainer(); - const foldersContainer = await buildFoldersContainer(); + const foldersContainer = await buildFoldersContainer(PlaceholderContainer); const { container: filesContainer } = await buildFilesContainer( foldersContainer ); diff --git a/src/workers/sync-engine/index.ts b/src/workers/sync-engine/index.ts index 5957490bc..1e015dc20 100644 --- a/src/workers/sync-engine/index.ts +++ b/src/workers/sync-engine/index.ts @@ -4,7 +4,6 @@ import { DependencyContainerFactory } from './dependency-injection/DependencyCon import packageJson from '../../../package.json'; import { BindingsManager } from './BindingManager'; import fs from 'fs/promises'; -import { buildControllers } from './callbacks-controllers/buildControllers'; import { iconPath } from 'workers/utils/icon'; async function ensureTheFolderExist(path: string) { diff --git a/src/workers/sync-engine/modules/folders/test/application/FolderMover.test.ts b/src/workers/sync-engine/modules/folders/test/application/FolderMover.test.ts index be0e98545..b52c1befa 100644 --- a/src/workers/sync-engine/modules/folders/test/application/FolderMover.test.ts +++ b/src/workers/sync-engine/modules/folders/test/application/FolderMover.test.ts @@ -2,19 +2,16 @@ import { FolderFinder } from '../../application/FolderFinder'; import { FolderMover } from '../../application/FolderMover'; import { FolderMother } from '../domain/FolderMother'; import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; -import { IpcRendererSyncEngineMock } from '../../../shared/test/__mock__/IpcRendererSyncEngineMock'; import { FolderPath } from '../../domain/FolderPath'; describe('Folder Mover', () => { let repository: FolderRepositoryMock; let folderFinder: FolderFinder; - let ipc: IpcRendererSyncEngineMock; let SUT: FolderMover; beforeEach(() => { repository = new FolderRepositoryMock(); folderFinder = new FolderFinder(repository); - ipc = new IpcRendererSyncEngineMock(); SUT = new FolderMover(repository, folderFinder); }); diff --git a/src/workers/sync-engine/modules/shared/domain/MapObserver.ts b/src/workers/sync-engine/modules/shared/domain/MapObserver.ts index f3c2a0a74..e22a3d7f2 100644 --- a/src/workers/sync-engine/modules/shared/domain/MapObserver.ts +++ b/src/workers/sync-engine/modules/shared/domain/MapObserver.ts @@ -3,11 +3,9 @@ export class MapObserver { private readonly mapToObserve: Map, private readonly callback: () => void, private intervalId: NodeJS.Timeout | null = null - ) { - this.startObserving(); - } + ) {} - private startObserving() { + startObserving() { if (this.intervalId === null) { this.intervalId = setInterval(() => { if (this.mapToObserve.size === 0) {