From b6af58f11ccdf02fbb7ed4f74228fe2030c58305 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 25 Jan 2024 18:10:14 +0100 Subject: [PATCH 01/15] chore: set back electron version to fix install issue --- package.json | 2 +- yarn.lock | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 2eed4aa31..e06ea2d19 100644 --- a/package.json +++ b/package.json @@ -199,7 +199,7 @@ "detect-port": "^1.3.0", "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", - "electron": "^25.8.4", + "electron": "^19.1.9", "electron-builder": "^23.6.0", "electron-devtools-installer": "^3.2.0", "electron-notarize": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index 70cbcb571..32a11cbe5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5384,12 +5384,10 @@ electron-updater@^4.6.4: lodash.isequal "^4.5.0" semver "^7.3.5" - -electron@^25.8.4: - version "25.8.4" - resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.4.tgz#b50877aac7d96323920437baf309ad86382cb455" - integrity sha512-hUYS3RGdaa6E1UWnzeGnsdsBYOggwMMg4WGxNGvAoWtmRrr6J1BsjFW/yRq4WsJHJce2HdzQXtz4OGXV6yUCLg== - +electron@^19.1.9: + version "19.1.9" + resolved "https://registry.yarnpkg.com/electron/-/electron-19.1.9.tgz#01995eea4014f7cdb2f616f5f3492d4ed6f5e4f0" + integrity sha512-XT5LkTzIHB+ZtD3dTmNnKjVBWrDWReCKt9G1uAFLz6uJMEVcIUiYO+fph5pLXETiBw/QZBx8egduMEfIccLx+g== dependencies: "@electron/get" "^1.14.1" "@types/node" "^16.11.26" From b9926dbb2bfdf15fb8b9ca09fc12828dd7a1adb2 Mon Sep 17 00:00:00 2001 From: Joan Vicens Date: Wed, 31 Jan 2024 12:08:36 +0000 Subject: [PATCH 02/15] [PB-1412] feat: download to plain file (#447) --- src/apps/fuse/FuseApp.ts | 7 -- .../fuse/callbacks/GetAttributesCallback.ts | 2 +- src/apps/fuse/callbacks/OpenCallback.ts | 12 --- src/apps/fuse/callbacks/ReleaseCallback.ts | 2 +- src/apps/fuse/callbacks/WriteCallback.ts | 4 +- .../FuseDependencyContainer.ts | 2 - .../FuseDependencyContainerFactory.ts | 7 -- .../FuseDomainEventSubscribers.ts | 1 - .../BoundaryBridge/BoundaryBridgeContainer.ts | 2 - .../boundaryBridgeContainerBuilder.ts | 16 ---- .../OfflineDriveDependencyContainer.ts | 4 +- .../offlineContentsContainerBuilder.ts | 20 ++-- ...{HttpClient.ts => AuthorizedHttpClient.ts} | 2 +- .../HttpClient/background-process-clients.ts | 2 +- .../HttpClient/http-client.test.skip.ts | 2 +- .../shared/HttpClient/main-process-client.ts | 2 +- .../application/OfflineFilePathRetriever.ts | 10 -- .../application/OfflineFileUploader.ts | 34 ------- ...Appended.ts => OfflineContentsAppender.ts} | 21 ++--- .../application/OfflineContentsCreator.ts | 3 +- .../OfflineContentsPathCalculator.ts | 10 -- .../application/OfflineContentsUploader.ts | 94 ++++++------------- .../contents/domain/OfflineContents.ts | 32 ++----- .../domain/OfflineContentsManagersFactory.ts | 22 ++--- ...neContentsId.ts => OfflineContentsName.ts} | 2 +- .../domain/OfflineContentsRepository.ts | 21 ++--- .../contents/domain/errors/IOError.ts | 2 + .../EnvironmentOfflineContentsUploader.ts | 9 +- ...onmentRemoteFileContentsManagersFactory.ts | 53 ++++++++++- .../NodeFSOfflineContentsRepository.ts | 72 ++++++-------- .../files/application/OfflineFileCreator.ts | 4 +- .../application/OfflineFileSizeIncreaser.ts | 5 +- .../offline-drive/files/domain/OfflineFile.ts | 24 ++--- .../files/domain/OfflineFileRepository.ts | 2 +- .../files/domain/OfflineFileSize.ts | 6 +- .../InMemoryOfflineFileRepository.ts | 6 +- .../shared/domain/DownloadProgressTracker.ts | 6 ++ .../domain/value-objects/DateValueObject.ts | 19 ++++ .../application/FileCreationOrchestrator.ts | 2 +- .../DownloadContentsToPlainFile.ts | 49 +++++----- .../application/LocalContentsMover.ts | 4 +- .../contents/domain/ContentsMetadata.ts | 30 ++++++ .../contents/domain/LocalFileSystem.ts | 6 ++ .../infrastructure/FSLocalFileSystem.ts | 11 +++ .../CreateFileOnOfflineFileUplodaded.ts | 5 +- .../files/application/FileCreator.ts | 10 +- .../infrastructure/SDKRemoteFileSystem.ts | 28 ++++-- .../__mocks__/OfflineFileRepositoryMock.ts | 4 +- .../OfflineFileSizeIncreaser.test.ts | 5 +- tests/context/shared/domain/DateMother.ts | 25 +++++ .../value-objects/DateValueObject.test.ts | 78 +++++++++++++++ .../__mocks__/ContentFileDownloaderMock.ts | 4 +- .../contents/__mocks__/LocalFileWriterMock.ts | 5 + .../application/ContentsDownloader._test.ts | 6 +- .../DownloadContentsToPlainFile.test.ts | 74 +++++++++++++++ .../contents/domain/ContentsMetadata.test.ts | 17 ++++ .../files/application/FileCreator.test.ts | 6 +- .../__mock__/DownloadProgressTrackerMock.ts | 21 +++++ 58 files changed, 552 insertions(+), 382 deletions(-) rename src/apps/shared/HttpClient/{HttpClient.ts => AuthorizedHttpClient.ts} (94%) delete mode 100644 src/context/offline-drive/boundaryBridge/application/OfflineFilePathRetriever.ts delete mode 100644 src/context/offline-drive/boundaryBridge/application/OfflineFileUploader.ts rename src/context/offline-drive/contents/application/{OfflineContentsAppended.ts => OfflineContentsAppender.ts} (62%) delete mode 100644 src/context/offline-drive/contents/application/OfflineContentsPathCalculator.ts rename src/context/offline-drive/contents/domain/{OfflineContentsId.ts => OfflineContentsName.ts} (57%) create mode 100644 src/context/offline-drive/contents/domain/errors/IOError.ts create mode 100644 src/context/shared/domain/DownloadProgressTracker.ts create mode 100644 src/context/virtual-drive/contents/domain/ContentsMetadata.ts create mode 100644 tests/context/shared/domain/DateMother.ts create mode 100644 tests/context/shared/domain/value-objects/DateValueObject.test.ts create mode 100644 tests/context/virtual-drive/contents/application/DownloadContentsToPlainFile.test.ts create mode 100644 tests/context/virtual-drive/contents/domain/ContentsMetadata.test.ts create mode 100644 tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts diff --git a/src/apps/fuse/FuseApp.ts b/src/apps/fuse/FuseApp.ts index 03cecbd24..14be2db07 100644 --- a/src/apps/fuse/FuseApp.ts +++ b/src/apps/fuse/FuseApp.ts @@ -4,8 +4,6 @@ import { GetAttributesCallback } from './callbacks/GetAttributesCallback'; import { OpenCallback } from './callbacks/OpenCallback'; import { ReadCallback } from './callbacks/ReadCallback'; import { RenameOrMoveCallback } from './callbacks/RenameOrMoveCallback'; -import { ListXAttributesCallback } from './callbacks/ListXAttributesCallback'; -import { GetXAttributeCallback } from './callbacks/GetXAttributeCallback'; import { CreateCallback } from './callbacks/CreateCallback'; import { MakeDirectoryCallback } from './callbacks/MakeDirectoryCallback'; import { TrashFileCallback } from './callbacks/TrashFileCallback'; @@ -29,8 +27,6 @@ export class FuseApp { ) {} private async getOpt() { - const listXAttributes = new ListXAttributesCallback(); - const getXAttribute = new GetXAttributeCallback('GetXAttribute'); const readdir = new ReaddirCallback( this.fuseContainer.virtualDriveContainer ); @@ -59,8 +55,6 @@ export class FuseApp { ); return { - listxattr: listXAttributes.execute.bind(listXAttributes), - getxattr: getXAttribute.execute.bind(getXAttribute), getattr: getattr.handle.bind(getattr), readdir: readdir.handle.bind(readdir), open: open.handle.bind(open), @@ -72,7 +66,6 @@ export class FuseApp { release: release.handle.bind(release), unlink: trashFile.handle.bind(trashFile), rmdir: trashFolder.handle.bind(trashFolder), - // statfs: statfs.execute.bind(statfs), }; } diff --git a/src/apps/fuse/callbacks/GetAttributesCallback.ts b/src/apps/fuse/callbacks/GetAttributesCallback.ts index 3af84b1f8..a2ddf100c 100644 --- a/src/apps/fuse/callbacks/GetAttributesCallback.ts +++ b/src/apps/fuse/callbacks/GetAttributesCallback.ts @@ -76,7 +76,7 @@ export class GetAttributesCallback extends FuseCallback { ); } - const alreadyDownloaded = await this.container.localContentChecker.run( - file - ); - - if (alreadyDownloaded) { - Logger.debug( - `[Local Cache] "${file.nameWithExtension}" contents are already in local` - ); - return this.right(file.id); - } - try { - Logger.debug(`"${file.nameWithExtension}" contents are not in local`); await this.container.downloadContentsToPlainFile.run(file); return this.right(file.id); diff --git a/src/apps/fuse/callbacks/ReleaseCallback.ts b/src/apps/fuse/callbacks/ReleaseCallback.ts index 953447bd7..15619b8fe 100644 --- a/src/apps/fuse/callbacks/ReleaseCallback.ts +++ b/src/apps/fuse/callbacks/ReleaseCallback.ts @@ -15,7 +15,7 @@ export class ReleaseCallback extends NotifyFuseCallback { } try { - await this.container.offlineFileUploader.run(file); + await this.container.offlineContentsUploader.run(file.id, file.path); return this.right(); } catch (err: unknown) { if (err instanceof Error) { diff --git a/src/apps/fuse/callbacks/WriteCallback.ts b/src/apps/fuse/callbacks/WriteCallback.ts index 0e35bb90a..31770c21f 100644 --- a/src/apps/fuse/callbacks/WriteCallback.ts +++ b/src/apps/fuse/callbacks/WriteCallback.ts @@ -8,10 +8,10 @@ export class WriteCallback { _fd: string, buffer: Buffer, len: number, - pos: number, + _pos: number, cb: (a: number) => void ) { - await this.container.offlineContentsAppender.run(path, buffer, len, pos); + await this.container.offlineContentsAppender.run(path, buffer); return cb(len); } diff --git a/src/apps/fuse/dependency-injection/FuseDependencyContainer.ts b/src/apps/fuse/dependency-injection/FuseDependencyContainer.ts index 70e7f1eda..0e28e2da9 100644 --- a/src/apps/fuse/dependency-injection/FuseDependencyContainer.ts +++ b/src/apps/fuse/dependency-injection/FuseDependencyContainer.ts @@ -1,9 +1,7 @@ import { OfflineDriveDependencyContainer } from './offline/OfflineDriveDependencyContainer'; -import { UserDependencyContainer } from './user/UserDependencyContainer'; import { VirtualDriveDependencyContainer } from './virtual-drive/VirtualDriveDependencyContainer'; export interface FuseDependencyContainer { offlineDriveContainer: OfflineDriveDependencyContainer; virtualDriveContainer: VirtualDriveDependencyContainer; - userContainer: UserDependencyContainer; } diff --git a/src/apps/fuse/dependency-injection/FuseDependencyContainerFactory.ts b/src/apps/fuse/dependency-injection/FuseDependencyContainerFactory.ts index 40aa63bda..12bb98384 100644 --- a/src/apps/fuse/dependency-injection/FuseDependencyContainerFactory.ts +++ b/src/apps/fuse/dependency-injection/FuseDependencyContainerFactory.ts @@ -2,13 +2,11 @@ import { FuseDependencyContainer } from './FuseDependencyContainer'; import { FuseDomainEventSubscribers } from './FuseDomainEventSubscribers'; import { DependencyInjectionEventBus } from './common/eventBus'; import { OfflineDriveDependencyContainerFactory } from './offline/OfflineDriveDependencyContainerFactory'; -import { UserContainerFactory } from './user/UserContainerFactory'; import { VirtualDriveDependencyContainerFactory } from './virtual-drive/VirtualDriveDependencyContainerFactory'; type FuseDependencyContainerFactorySubscribers = Array< | keyof FuseDependencyContainer['offlineDriveContainer'] | keyof FuseDependencyContainer['virtualDriveContainer'] - | keyof FuseDependencyContainer['userContainer'] >; export class FuseDependencyContainerFactory { @@ -17,7 +15,6 @@ export class FuseDependencyContainerFactory { static readonly subscribers: FuseDependencyContainerFactorySubscribers = [ ...VirtualDriveDependencyContainerFactory.subscribers, ...OfflineDriveDependencyContainerFactory.subscribers, - ...UserContainerFactory.subscribers, ]; eventSubscribers( @@ -43,13 +40,9 @@ export class FuseDependencyContainerFactory { new OfflineDriveDependencyContainerFactory(); const offlineDriveContainer = await offlineDriveContainerFactory.build(); - const userContainerFactory = new UserContainerFactory(); - const userContainer = await userContainerFactory.build(); - const container = { offlineDriveContainer, virtualDriveContainer, - userContainer, }; bus.addSubscribers(FuseDomainEventSubscribers.from(container)); diff --git a/src/apps/fuse/dependency-injection/FuseDomainEventSubscribers.ts b/src/apps/fuse/dependency-injection/FuseDomainEventSubscribers.ts index 288e3966e..db66dbd2b 100644 --- a/src/apps/fuse/dependency-injection/FuseDomainEventSubscribers.ts +++ b/src/apps/fuse/dependency-injection/FuseDomainEventSubscribers.ts @@ -10,7 +10,6 @@ export class FuseDomainEventSubscribers { const plainContainer = { ...container.offlineDriveContainer, ...container.virtualDriveContainer, - ...container.userContainer, }; const subscribers = FuseDependencyContainerFactory.subscribers.map( diff --git a/src/apps/fuse/dependency-injection/offline/BoundaryBridge/BoundaryBridgeContainer.ts b/src/apps/fuse/dependency-injection/offline/BoundaryBridge/BoundaryBridgeContainer.ts index 0e0c0b5ad..36cac87d1 100644 --- a/src/apps/fuse/dependency-injection/offline/BoundaryBridge/BoundaryBridgeContainer.ts +++ b/src/apps/fuse/dependency-injection/offline/BoundaryBridge/BoundaryBridgeContainer.ts @@ -1,7 +1,5 @@ import { OfflineFileAndContentsCreator } from '../../../../../context/offline-drive/boundaryBridge/application/OfflineFileAndContentsCreator'; -import { OfflineFileUploader } from '../../../../../context/offline-drive/boundaryBridge/application/OfflineFileUploader'; export interface BoundaryBridgeContainer { - offlineFileUploader: OfflineFileUploader; offlineFileAndContentsCreator: OfflineFileAndContentsCreator; } diff --git a/src/apps/fuse/dependency-injection/offline/BoundaryBridge/boundaryBridgeContainerBuilder.ts b/src/apps/fuse/dependency-injection/offline/BoundaryBridge/boundaryBridgeContainerBuilder.ts index f4f25cda7..181e2990e 100644 --- a/src/apps/fuse/dependency-injection/offline/BoundaryBridge/boundaryBridgeContainerBuilder.ts +++ b/src/apps/fuse/dependency-injection/offline/BoundaryBridge/boundaryBridgeContainerBuilder.ts @@ -1,7 +1,4 @@ import { OfflineFileAndContentsCreator } from '../../../../../context/offline-drive/boundaryBridge/application/OfflineFileAndContentsCreator'; -import { OfflineFilePathRetriever } from '../../../../../context/offline-drive/boundaryBridge/application/OfflineFilePathRetriever'; -import { OfflineFileUploader } from '../../../../../context/offline-drive/boundaryBridge/application/OfflineFileUploader'; -import { DependencyInjectionEventBus } from '../../common/eventBus'; import { OfflineContentsDependencyContainer } from '../OfflineContents/OfflineDriveDependencyContainer'; import { OfflineFilesContainer } from '../OfflineFiles/OfflineFilesContainer'; import { BoundaryBridgeContainer } from './BoundaryBridgeContainer'; @@ -10,25 +7,12 @@ export async function buildBoundaryBridgeContainer( offlineFileContainer: OfflineFilesContainer, offlineContentsContainer: OfflineContentsDependencyContainer ): Promise { - const { bus } = DependencyInjectionEventBus; - - const offlineFilePathRetriever = new OfflineFilePathRetriever( - offlineContentsContainer.offlineContentsPathCalculator - ); - - const offlineFileUploader = new OfflineFileUploader( - offlineFilePathRetriever, - offlineContentsContainer.offlineContentsUploader, - bus - ); - const offlineFileAndContentsCreator = new OfflineFileAndContentsCreator( offlineFileContainer.offlineFileCreator, offlineContentsContainer.offlineContentsCreator ); return { - offlineFileUploader, offlineFileAndContentsCreator, }; } diff --git a/src/apps/fuse/dependency-injection/offline/OfflineContents/OfflineDriveDependencyContainer.ts b/src/apps/fuse/dependency-injection/offline/OfflineContents/OfflineDriveDependencyContainer.ts index decbe7adb..4492b6b84 100644 --- a/src/apps/fuse/dependency-injection/offline/OfflineContents/OfflineDriveDependencyContainer.ts +++ b/src/apps/fuse/dependency-injection/offline/OfflineContents/OfflineDriveDependencyContainer.ts @@ -1,11 +1,9 @@ -import { OfflineContentsAppender } from '../../../../../context/offline-drive/contents/application/OfflineContentsAppended'; +import { OfflineContentsAppender } from '../../../../../context/offline-drive/contents/application/OfflineContentsAppender'; import { OfflineContentsCreator } from '../../../../../context/offline-drive/contents/application/OfflineContentsCreator'; -import { OfflineContentsPathCalculator } from '../../../../../context/offline-drive/contents/application/OfflineContentsPathCalculator'; import { OfflineContentsUploader } from '../../../../../context/offline-drive/contents/application/OfflineContentsUploader'; export interface OfflineContentsDependencyContainer { offlineContentsCreator: OfflineContentsCreator; offlineContentsAppender: OfflineContentsAppender; offlineContentsUploader: OfflineContentsUploader; - offlineContentsPathCalculator: OfflineContentsPathCalculator; } diff --git a/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts b/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts index 248ee0d94..9d7513f19 100644 --- a/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts +++ b/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts @@ -1,11 +1,11 @@ -import { OfflineContentsAppender } from '../../../../../context/offline-drive/contents/application/OfflineContentsAppended'; +import { OfflineContentsAppender } from '../../../../../context/offline-drive/contents/application/OfflineContentsAppender'; import { OfflineContentsCreator } from '../../../../../context/offline-drive/contents/application/OfflineContentsCreator'; -import { OfflineContentsPathCalculator } from '../../../../../context/offline-drive/contents/application/OfflineContentsPathCalculator'; import { OfflineContentsUploader } from '../../../../../context/offline-drive/contents/application/OfflineContentsUploader'; import { EnvironmentOfflineContentsManagersFactory } from '../../../../../context/offline-drive/contents/infrastructure/EnvironmentRemoteFileContentsManagersFactory'; import { NodeFSOfflineContentsRepository } from '../../../../../context/offline-drive/contents/infrastructure/NodeFSOfflineContentsRepository'; import { MainProcessUploadProgressTracker } from '../../../../../context/shared/infrastructure/MainProcessUploadProgressTracker'; import { FuseAppDataLocalFileContentsDirectoryProvider } from '../../../../../context/virtual-drive/shared/infrastructure/LocalFileContentsDirectoryProviders/FuseAppDataLocalFileContentsDirectoryProvider'; +import { DependencyInjectionEventBus } from '../../../../fuse/dependency-injection/common/eventBus'; import { DependencyInjectionInxtEnvironment } from '../../common/inxt-environment'; import { DependencyInjectionUserProvider } from '../../common/user'; import { OfflineFilesContainer } from '../OfflineFiles/OfflineFilesContainer'; @@ -16,6 +16,7 @@ export async function buildOfflineContentsContainer( ): Promise { const environment = DependencyInjectionInxtEnvironment.get(); const user = DependencyInjectionUserProvider.get(); + const eventBus = DependencyInjectionEventBus.bus; const localFileContentsDirectoryProvider = new FuseAppDataLocalFileContentsDirectoryProvider(); @@ -34,17 +35,17 @@ export async function buildOfflineContentsContainer( repository ); - const offlineContentsPathCalculator = new OfflineContentsPathCalculator( - repository - ); - const environmentOfflineContentsManagersFactory = - new EnvironmentOfflineContentsManagersFactory(environment, user.bucket); + new EnvironmentOfflineContentsManagersFactory( + environment, + user.bucket, + tracker + ); const offlineContentsUploader = new OfflineContentsUploader( - environmentOfflineContentsManagersFactory, repository, - tracker + environmentOfflineContentsManagersFactory, + eventBus ); const offlineContentsCreator = new OfflineContentsCreator(repository); @@ -52,7 +53,6 @@ export async function buildOfflineContentsContainer( return { offlineContentsCreator, offlineContentsAppender, - offlineContentsPathCalculator, offlineContentsUploader, }; } diff --git a/src/apps/shared/HttpClient/HttpClient.ts b/src/apps/shared/HttpClient/AuthorizedHttpClient.ts similarity index 94% rename from src/apps/shared/HttpClient/HttpClient.ts rename to src/apps/shared/HttpClient/AuthorizedHttpClient.ts index ed0361dc9..071f1989a 100644 --- a/src/apps/shared/HttpClient/HttpClient.ts +++ b/src/apps/shared/HttpClient/AuthorizedHttpClient.ts @@ -27,7 +27,7 @@ export class AuthorizedHttpClient { private handleUnauthorizedResponse(error: AxiosError) { if (error?.response?.status === 401) { - Logger.warn('[AUTH] Request unauthorized'); + Logger.warn('[AUTH] Request unauthorized', error.config.url); this.unauthorizedNotifier(); } diff --git a/src/apps/shared/HttpClient/background-process-clients.ts b/src/apps/shared/HttpClient/background-process-clients.ts index 29d893bb8..51255f274 100644 --- a/src/apps/shared/HttpClient/background-process-clients.ts +++ b/src/apps/shared/HttpClient/background-process-clients.ts @@ -1,7 +1,7 @@ import { ipcRenderer } from 'electron'; import { AuthorizedClients } from './Clients'; -import { AuthorizedHttpClient } from './HttpClient'; +import { AuthorizedHttpClient } from './AuthorizedHttpClient'; export const onUserUnauthorized = () => ipcRenderer.emit('user-is-unauthorized'); diff --git a/src/apps/shared/HttpClient/http-client.test.skip.ts b/src/apps/shared/HttpClient/http-client.test.skip.ts index 024f2def1..816d86aa5 100644 --- a/src/apps/shared/HttpClient/http-client.test.skip.ts +++ b/src/apps/shared/HttpClient/http-client.test.skip.ts @@ -1,7 +1,7 @@ import { AxiosInstance } from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { AuthorizedHttpClient } from './HttpClient'; +import { AuthorizedHttpClient } from './AuthorizedHttpClient'; const URL = 'http://jibuwsik.ie/ugco'; diff --git a/src/apps/shared/HttpClient/main-process-client.ts b/src/apps/shared/HttpClient/main-process-client.ts index cc07d288d..1af852568 100644 --- a/src/apps/shared/HttpClient/main-process-client.ts +++ b/src/apps/shared/HttpClient/main-process-client.ts @@ -3,7 +3,7 @@ import { Axios } from 'axios'; import { onUserUnauthorized } from '../../main/auth/handlers'; import { getHeaders, getNewApiHeaders } from '../../main/auth/service'; import { AuthorizedClients } from './Clients'; -import { AuthorizedHttpClient } from './HttpClient'; +import { AuthorizedHttpClient } from './AuthorizedHttpClient'; const headersProvider = () => Promise.resolve(getHeaders(false)); const newHeadersProvider = () => Promise.resolve(getNewApiHeaders()); diff --git a/src/context/offline-drive/boundaryBridge/application/OfflineFilePathRetriever.ts b/src/context/offline-drive/boundaryBridge/application/OfflineFilePathRetriever.ts deleted file mode 100644 index 32021652b..000000000 --- a/src/context/offline-drive/boundaryBridge/application/OfflineFilePathRetriever.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OfflineContentsPathCalculator } from '../../contents/application/OfflineContentsPathCalculator'; -import { OfflineFile } from '../../files/domain/OfflineFile'; - -export class OfflineFilePathRetriever { - constructor(private readonly pathCalculator: OfflineContentsPathCalculator) {} - - async run(file: OfflineFile): Promise { - return await this.pathCalculator.run(file.id); - } -} diff --git a/src/context/offline-drive/boundaryBridge/application/OfflineFileUploader.ts b/src/context/offline-drive/boundaryBridge/application/OfflineFileUploader.ts deleted file mode 100644 index ed8d4526f..000000000 --- a/src/context/offline-drive/boundaryBridge/application/OfflineFileUploader.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { EventBus } from '../../../virtual-drive/shared/domain/EventBus'; -import { OfflineContentsUploader } from '../../contents/application/OfflineContentsUploader'; -import { OfflineContentsUploadedDomainEvent } from '../../contents/domain/events/OfflineContentsUploadedDomainEvent'; -import { OfflineFile } from '../../files/domain/OfflineFile'; -import { OfflineFilePathRetriever } from './OfflineFilePathRetriever'; - -export class OfflineFileUploader { - constructor( - private readonly offlineFilePathRetriever: OfflineFilePathRetriever, - private readonly contentsUploader: OfflineContentsUploader, - private readonly eventBus: EventBus - ) {} - - async run(file: OfflineFile): Promise { - const absolutePath = await this.offlineFilePathRetriever.run(file); - - const { name, extension } = file; - - const contentsId = await this.contentsUploader.run( - name, - extension, - absolutePath - ); - - const contentsUploadedEvent = new OfflineContentsUploadedDomainEvent({ - aggregateId: contentsId, - size: file.size, - path: file.path, - offlineContentsPath: absolutePath, - }); - - await this.eventBus.publish([contentsUploadedEvent]); - } -} diff --git a/src/context/offline-drive/contents/application/OfflineContentsAppended.ts b/src/context/offline-drive/contents/application/OfflineContentsAppender.ts similarity index 62% rename from src/context/offline-drive/contents/application/OfflineContentsAppended.ts rename to src/context/offline-drive/contents/application/OfflineContentsAppender.ts index 2ceadd746..a4af6b771 100644 --- a/src/context/offline-drive/contents/application/OfflineContentsAppended.ts +++ b/src/context/offline-drive/contents/application/OfflineContentsAppender.ts @@ -1,6 +1,7 @@ import { OfflineFileFinder } from '../../files/application/OfflineFileFinder'; import { OfflineFileSizeIncreaser } from '../../files/application/OfflineFileSizeIncreaser'; import { OfflineContentsRepository } from '../domain/OfflineContentsRepository'; +import { OfflineContentsIOError } from '../domain/errors/IOError'; export class OfflineContentsAppender { constructor( @@ -9,21 +10,15 @@ export class OfflineContentsAppender { private readonly contentsRepository: OfflineContentsRepository ) {} - async run( - path: string, - buffer: Buffer, - length: number, - position: number - ): Promise { + async run(path: string, buffer: Buffer): Promise { const file = await this.offlineFileFinder.run({ path }); - await this.contentsRepository.writeToFile( - file.id, - buffer, - length, - position - ); + try { + await this.contentsRepository.writeToFile(file.id, buffer); + } catch (error: unknown) { + throw new OfflineContentsIOError(); + } - await this.offlineFileSizeIncreaser.run(file.id, length); + await this.offlineFileSizeIncreaser.run(file.id, buffer.length); } } diff --git a/src/context/offline-drive/contents/application/OfflineContentsCreator.ts b/src/context/offline-drive/contents/application/OfflineContentsCreator.ts index 764bcfef3..42d4a8bd9 100644 --- a/src/context/offline-drive/contents/application/OfflineContentsCreator.ts +++ b/src/context/offline-drive/contents/application/OfflineContentsCreator.ts @@ -1,9 +1,10 @@ +import { OfflineFileId } from '../../files/domain/OfflineFileId'; import { OfflineContentsRepository } from '../domain/OfflineContentsRepository'; export class OfflineContentsCreator { constructor(private readonly repository: OfflineContentsRepository) {} - async run(name: string): Promise { + async run(name: OfflineFileId): Promise { await this.repository.createEmptyFile(name); } } diff --git a/src/context/offline-drive/contents/application/OfflineContentsPathCalculator.ts b/src/context/offline-drive/contents/application/OfflineContentsPathCalculator.ts deleted file mode 100644 index 1efc7a0a1..000000000 --- a/src/context/offline-drive/contents/application/OfflineContentsPathCalculator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OfflineFileAttributes } from '../../files/domain/OfflineFile'; -import { OfflineContentsRepository } from '../domain/OfflineContentsRepository'; - -export class OfflineContentsPathCalculator { - constructor(private readonly repository: OfflineContentsRepository) {} - - async run(id: OfflineFileAttributes['id']): Promise { - return this.repository.getAbsolutePath(id); - } -} diff --git a/src/context/offline-drive/contents/application/OfflineContentsUploader.ts b/src/context/offline-drive/contents/application/OfflineContentsUploader.ts index 750e030a0..f87426f68 100644 --- a/src/context/offline-drive/contents/application/OfflineContentsUploader.ts +++ b/src/context/offline-drive/contents/application/OfflineContentsUploader.ts @@ -1,78 +1,40 @@ -import { UploadProgressTracker } from '../../../shared/domain/UploadProgressTracker'; -import { - OfflineContentUploader, - OfflineContentsManagersFactory, -} from '../domain/OfflineContentsManagersFactory'; +import { EventBus } from '../../../virtual-drive/shared/domain/EventBus'; +import { OfflineContentsManagersFactory } from '../domain/OfflineContentsManagersFactory'; import { OfflineContentsRepository } from '../domain/OfflineContentsRepository'; -import Logger from 'electron-log'; +import { OfflineContentsUploadedDomainEvent } from '../domain/events/OfflineContentsUploadedDomainEvent'; +import { FilePath } from '../../../virtual-drive/files/domain/FilePath'; +import { OfflineContentsName } from '../domain/OfflineContentsName'; export class OfflineContentsUploader { constructor( - private readonly contentsManagersFactory: OfflineContentsManagersFactory, private readonly repository: OfflineContentsRepository, - private readonly progressTracker: UploadProgressTracker + private readonly contentsManagersFactory: OfflineContentsManagersFactory, + private readonly eventBus: EventBus ) {} - private registerEvents( - name: string, - extension: string, - size: number, - uploader: OfflineContentUploader - ) { - uploader.on('start', () => { - this.progressTracker.uploadStarted(name, extension, size, { - elapsedTime: uploader.elapsedTime(), - }); - Logger.debug('FILE_UPLOADING', { - processInfo: { elapsedTime: uploader.elapsedTime() }, - }); - }); - - uploader.on('progress', (progress: number) => { - this.progressTracker.uploadProgress(name, extension, size, { - elapsedTime: uploader.elapsedTime(), - progress, - }); - Logger.debug('FILE_UPLOADING', { - processInfo: { elapsedTime: uploader.elapsedTime(), progress }, - }); + async run(name: OfflineContentsName, path: FilePath): Promise { + const { contents, stream, abortSignal } = await this.repository.read(name); + + const uploader = this.contentsManagersFactory.uploader( + stream, + contents, + { + name: path.name(), + extension: path.extension(), + }, + abortSignal + ); + + const contentsId = await uploader(); + + const contentsUploadedEvent = new OfflineContentsUploadedDomainEvent({ + aggregateId: contentsId, + offlineContentsPath: contents.absolutePath, + size: contents.size, + path: path.value, }); - uploader.on('error', (error: Error) => { - this.progressTracker.uploadError(name, extension, error.message); - Logger.debug('FILE_UPLOAD_ERROR', { - error: error.message, - }); - }); - - uploader.on('finish', () => { - this.progressTracker.uploadCompleted(name, extension, size, { - elapsedTime: uploader.elapsedTime(), - }); - Logger.debug('FILE_UPLOADED', { - processInfo: { elapsedTime: uploader.elapsedTime() }, - }); - }); - } - - async run( - name: string, - extension: string, - absolutePath: string - ): Promise { - const { - contents: readable, - abortSignal, - size, - } = await this.repository.provide(absolutePath); - - const uploader = this.contentsManagersFactory.uploader(size, abortSignal); - - this.registerEvents(name, extension, size, uploader); - - const contentsId = await uploader.upload(readable, size); - - Logger.debug('FILE UPLOADED WITH ID: ', contentsId); + await this.eventBus.publish([contentsUploadedEvent]); return contentsId; } diff --git a/src/context/offline-drive/contents/domain/OfflineContents.ts b/src/context/offline-drive/contents/domain/OfflineContents.ts index c601d8efa..78cf64f7d 100644 --- a/src/context/offline-drive/contents/domain/OfflineContents.ts +++ b/src/context/offline-drive/contents/domain/OfflineContents.ts @@ -1,39 +1,26 @@ -import { Readable } from 'stream'; import { AggregateRoot } from '../../../shared/domain/AggregateRoot'; +import { OfflineContentsName } from './OfflineContentsName'; import { OfflineContentsSize } from './OfflineContentsSize'; export type LocalFileContentsAttributes = { name: string; - extension: string; size: number; birthTime: number; modifiedTime: number; - contents: Readable; + absolutePath: string; }; export class OfflineContents extends AggregateRoot { private constructor( - private readonly _name: string, - private readonly _extension: string, + private _name: OfflineContentsName, private readonly _size: OfflineContentsSize, private readonly _birthTime: number, private readonly _modifiedTime: number, - public readonly stream: Readable + public readonly absolutePath: string ) { super(); } - public get name(): string { - return this._name; - } - public get extension(): string { - return this._extension; - } - - public get nameWithExtension(): string { - return this.name + (this.extension.length >= 0 ? '.' + this.extension : ''); - } - public get size(): number { return this._size.value; } @@ -48,24 +35,23 @@ export class OfflineContents extends AggregateRoot { static from(attributes: LocalFileContentsAttributes): OfflineContents { const remoteContents = new OfflineContents( - attributes.name, - attributes.extension, + new OfflineContentsName(attributes.name), new OfflineContentsSize(attributes.size), attributes.birthTime, attributes.modifiedTime, - attributes.contents + attributes.absolutePath ); return remoteContents; } - attributes(): Omit { + attributes(): LocalFileContentsAttributes { return { - name: this.name, - extension: this.extension, + name: this._name.value, size: this.size, birthTime: this.birthTime, modifiedTime: this.modifiedTime, + absolutePath: this.absolutePath, }; } } diff --git a/src/context/offline-drive/contents/domain/OfflineContentsManagersFactory.ts b/src/context/offline-drive/contents/domain/OfflineContentsManagersFactory.ts index fc1b14c78..8b2771665 100644 --- a/src/context/offline-drive/contents/domain/OfflineContentsManagersFactory.ts +++ b/src/context/offline-drive/contents/domain/OfflineContentsManagersFactory.ts @@ -1,4 +1,5 @@ import { Readable } from 'stream'; +import { OfflineContents } from './OfflineContents'; type ContentsId = string; @@ -9,17 +10,16 @@ export type OfflineContentsUploadEvents = { error: (error: Error) => void; }; -export interface OfflineContentUploader { - upload(contents: Readable, size: number): Promise; - - on( - event: keyof OfflineContentsUploadEvents, - fn: OfflineContentsUploadEvents[keyof OfflineContentsUploadEvents] - ): void; - - elapsedTime(): number; -} +export type OfflineContentUploader = () => Promise; export interface OfflineContentsManagersFactory { - uploader(size: number, abortSignal?: AbortSignal): OfflineContentUploader; + uploader( + readable: Readable, + contents: OfflineContents, + desiredPathElements: { + name: string; + extension: string; + }, + abortSignal?: AbortSignal + ): OfflineContentUploader; } diff --git a/src/context/offline-drive/contents/domain/OfflineContentsId.ts b/src/context/offline-drive/contents/domain/OfflineContentsName.ts similarity index 57% rename from src/context/offline-drive/contents/domain/OfflineContentsId.ts rename to src/context/offline-drive/contents/domain/OfflineContentsName.ts index f71728522..5ceffe230 100644 --- a/src/context/offline-drive/contents/domain/OfflineContentsId.ts +++ b/src/context/offline-drive/contents/domain/OfflineContentsName.ts @@ -1,3 +1,3 @@ import { Uuid } from '../../../shared/domain/value-objects/Uuid'; -export class OfflineContentsId extends Uuid {} +export class OfflineContentsName extends Uuid {} diff --git a/src/context/offline-drive/contents/domain/OfflineContentsRepository.ts b/src/context/offline-drive/contents/domain/OfflineContentsRepository.ts index 0a8d4daa1..845dd9bcf 100644 --- a/src/context/offline-drive/contents/domain/OfflineContentsRepository.ts +++ b/src/context/offline-drive/contents/domain/OfflineContentsRepository.ts @@ -1,21 +1,18 @@ import { Readable } from 'stream'; -import { OfflineFileAttributes } from '../../files/domain/OfflineFile'; +import { OfflineFile } from '../../files/domain/OfflineFile'; +import { OfflineContents } from './OfflineContents'; +import { OfflineContentsName } from './OfflineContentsName'; export interface OfflineContentsRepository { - writeToFile( - id: OfflineFileAttributes['id'], - buffer: Buffer, - length: number, - position: number - ): Promise; + writeToFile(id: OfflineFile['id'], buffer: Buffer): Promise; - createEmptyFile(id: OfflineFileAttributes['id']): Promise; + createEmptyFile(id: OfflineFile['id']): Promise; - getAbsolutePath(id: OfflineFileAttributes['id']): Promise; + getAbsolutePath(id: OfflineContentsName): Promise; - provide: (path: string) => Promise<{ - contents: Readable; - size: number; + read: (offlineContentsName: OfflineContentsName) => Promise<{ + contents: OfflineContents; + stream: Readable; abortSignal: AbortSignal; }>; } diff --git a/src/context/offline-drive/contents/domain/errors/IOError.ts b/src/context/offline-drive/contents/domain/errors/IOError.ts new file mode 100644 index 000000000..dcc6520a7 --- /dev/null +++ b/src/context/offline-drive/contents/domain/errors/IOError.ts @@ -0,0 +1,2 @@ +// TODO: extend form DriveDesktopError +export class OfflineContentsIOError extends Error {} diff --git a/src/context/offline-drive/contents/infrastructure/EnvironmentOfflineContentsUploader.ts b/src/context/offline-drive/contents/infrastructure/EnvironmentOfflineContentsUploader.ts index f8959539b..1997a3f16 100644 --- a/src/context/offline-drive/contents/infrastructure/EnvironmentOfflineContentsUploader.ts +++ b/src/context/offline-drive/contents/infrastructure/EnvironmentOfflineContentsUploader.ts @@ -1,14 +1,9 @@ import { UploadStrategyFunction } from '@internxt/inxt-js/build/lib/core/upload/strategy'; import { EventEmitter, Readable } from 'stream'; import { Stopwatch } from '../../../../apps/shared/types/Stopwatch'; -import { - OfflineContentUploader, - OfflineContentsUploadEvents, -} from '../domain/OfflineContentsManagersFactory'; +import { OfflineContentsUploadEvents } from '../domain/OfflineContentsManagersFactory'; -export class EnvironmentOfflineContentsUploader - implements OfflineContentUploader -{ +export class EnvironmentOfflineContentsUploader { private eventEmitter: EventEmitter; private stopwatch: Stopwatch; diff --git a/src/context/offline-drive/contents/infrastructure/EnvironmentRemoteFileContentsManagersFactory.ts b/src/context/offline-drive/contents/infrastructure/EnvironmentRemoteFileContentsManagersFactory.ts index edec548a2..1aac34998 100644 --- a/src/context/offline-drive/contents/infrastructure/EnvironmentRemoteFileContentsManagersFactory.ts +++ b/src/context/offline-drive/contents/infrastructure/EnvironmentRemoteFileContentsManagersFactory.ts @@ -4,6 +4,9 @@ import { OfflineContentsManagersFactory, } from '../domain/OfflineContentsManagersFactory'; import { EnvironmentOfflineContentsUploader } from './EnvironmentOfflineContentsUploader'; +import { OfflineContents } from '../domain/OfflineContents'; +import { UploadProgressTracker } from '../../../shared/domain/UploadProgressTracker'; +import { Readable } from 'stream'; export class EnvironmentOfflineContentsManagersFactory implements OfflineContentsManagersFactory @@ -12,17 +15,57 @@ export class EnvironmentOfflineContentsManagersFactory constructor( private readonly environment: Environment, - private readonly bucket: string + private readonly bucket: string, + private readonly progressTracker: UploadProgressTracker ) {} - uploader(size: number, abortSignal?: AbortSignal): OfflineContentUploader { - size; + uploader( + stream: Readable, + contents: OfflineContents, + { + name, + extension, + }: { + name: string; + extension: string; + }, + abortSignal?: AbortSignal + ): OfflineContentUploader { const fn = - size > + contents.size > EnvironmentOfflineContentsManagersFactory.MULTIPART_UPLOAD_SIZE_THRESHOLD ? this.environment.uploadMultipartFile : this.environment.upload; - return new EnvironmentOfflineContentsUploader(fn, this.bucket, abortSignal); + const uploader = new EnvironmentOfflineContentsUploader( + fn, + this.bucket, + abortSignal + ); + + uploader.on('start', () => { + this.progressTracker.uploadStarted(name, extension, contents.size, { + elapsedTime: uploader.elapsedTime(), + }); + }); + + uploader.on('progress', (progress: number) => { + this.progressTracker.uploadProgress(name, extension, contents.size, { + elapsedTime: uploader.elapsedTime(), + progress, + }); + }); + + uploader.on('error', (error: Error) => { + this.progressTracker.uploadError(name, extension, error.message); + }); + + uploader.on('finish', () => { + this.progressTracker.uploadCompleted(name, extension, contents.size, { + elapsedTime: uploader.elapsedTime(), + }); + }); + + return () => uploader.upload(stream, contents.size); } } diff --git a/src/context/offline-drive/contents/infrastructure/NodeFSOfflineContentsRepository.ts b/src/context/offline-drive/contents/infrastructure/NodeFSOfflineContentsRepository.ts index 1c207c107..fd0cf8537 100644 --- a/src/context/offline-drive/contents/infrastructure/NodeFSOfflineContentsRepository.ts +++ b/src/context/offline-drive/contents/infrastructure/NodeFSOfflineContentsRepository.ts @@ -1,11 +1,13 @@ import fs, { createReadStream, watch } from 'fs'; import { stat as statPromises } from 'fs/promises'; import { OfflineContentsRepository } from '../domain/OfflineContentsRepository'; -import { OfflineFileAttributes } from '../../files/domain/OfflineFile'; +import { OfflineFile } from '../../files/domain/OfflineFile'; import { LocalFileContentsDirectoryProvider } from '../../../virtual-drive/shared/domain/LocalFileContentsDirectoryProvider'; import path from 'path'; import Logger from 'electron-log'; import { Readable } from 'stream'; +import { OfflineContents } from '../domain/OfflineContents'; +import { OfflineContentsName } from '../domain/OfflineContentsName'; export class NodeFSOfflineContentsRepository implements OfflineContentsRepository @@ -21,10 +23,10 @@ export class NodeFSOfflineContentsRepository return path.join(location, this.subfolder); } - private async filePath(id: string): Promise { + private async filePath(name: OfflineContentsName): Promise { const folder = await this.folderPath(); - return path.join(folder, id); + return path.join(folder, name.value); } private createAbortableStream(filePath: string): { @@ -44,47 +46,13 @@ export class NodeFSOfflineContentsRepository fs.mkdirSync(folder, { recursive: true }); } - async writeToFile( - id: OfflineFileAttributes['id'], - buffer: Buffer, - length: number, - position: number - ): Promise { + async writeToFile(id: OfflineFile['id'], buffer: Buffer): Promise { const file = await this.filePath(id); - return new Promise((resolve) => { - fs.open(file, 'w', (err, fileDescriptor) => { - if (err) { - throw err; - } - - const dataBuffer = Buffer.from(buffer.slice(0, length)); - - fs.write( - fileDescriptor, - dataBuffer, - 0, - length, - position, - (writeErr) => { - if (writeErr) { - throw writeErr; - } - - fs.close(fileDescriptor, () => { - if (writeErr) { - throw writeErr; // TODO: is this needed? - } - - resolve(); - }); - } - ); - }); - }); + fs.appendFileSync(file, buffer); } - async createEmptyFile(id: string): Promise { + async createEmptyFile(id: OfflineFile['id']): Promise { const file = await this.filePath(id); return new Promise((resolve) => { @@ -99,15 +67,21 @@ export class NodeFSOfflineContentsRepository }); } - async getAbsolutePath(id: string): Promise { + async getAbsolutePath(id: OfflineFile['id']): Promise { return this.filePath(id); } - async provide(absoluteFilePath: string) { + async read(offlineContentsName: OfflineContentsName): Promise<{ + contents: OfflineContents; + stream: Readable; + abortSignal: AbortSignal; + }> { + const absoluteFilePath = await this.getAbsolutePath(offlineContentsName); + const { readable, controller } = this.createAbortableStream(absoluteFilePath); - const { size } = await statPromises(absoluteFilePath); + const { size, mtimeMs, birthtimeMs } = await statPromises(absoluteFilePath); const absoluteFolderPath = path.dirname(absoluteFilePath); const nameWithExtension = path.basename(absoluteFilePath); @@ -128,9 +102,17 @@ export class NodeFSOfflineContentsRepository watcher.close(); }); - return { - contents: readable, + const contents = OfflineContents.from({ + name: offlineContentsName.value, size, + modifiedTime: mtimeMs, + birthTime: birthtimeMs, + absolutePath: absoluteFilePath, + }); + + return { + contents, + stream: readable, abortSignal: controller.signal, }; } diff --git a/src/context/offline-drive/files/application/OfflineFileCreator.ts b/src/context/offline-drive/files/application/OfflineFileCreator.ts index 4b446eefc..68428a6f3 100644 --- a/src/context/offline-drive/files/application/OfflineFileCreator.ts +++ b/src/context/offline-drive/files/application/OfflineFileCreator.ts @@ -1,8 +1,8 @@ import { FilePath } from '../../../virtual-drive/files/domain/FilePath'; -import { FileSize } from '../../../virtual-drive/files/domain/FileSize'; import { EventBus } from '../../../virtual-drive/shared/domain/EventBus'; import { OfflineFile } from '../domain/OfflineFile'; import { OfflineFileRepository } from '../domain/OfflineFileRepository'; +import { OfflineFileSize } from '../domain/OfflineFileSize'; export class OfflineFileCreator { constructor( @@ -12,7 +12,7 @@ export class OfflineFileCreator { async run(path: string): Promise { const filePath = new FilePath(path); - const size = new FileSize(0); + const size = new OfflineFileSize(0); const file = OfflineFile.create(filePath, size); diff --git a/src/context/offline-drive/files/application/OfflineFileSizeIncreaser.ts b/src/context/offline-drive/files/application/OfflineFileSizeIncreaser.ts index f8f4a59eb..3215d1692 100644 --- a/src/context/offline-drive/files/application/OfflineFileSizeIncreaser.ts +++ b/src/context/offline-drive/files/application/OfflineFileSizeIncreaser.ts @@ -1,10 +1,11 @@ +import { OfflineFileId } from '../domain/OfflineFileId'; import { OfflineFileRepository } from '../domain/OfflineFileRepository'; export class OfflineFileSizeIncreaser { constructor(private readonly repository: OfflineFileRepository) {} - async run(id: string, amount: number): Promise { - const file = await this.repository.searchByPartial({ id }); + async run(id: OfflineFileId, amount: number): Promise { + const file = await this.repository.searchByPartial({ id: id.value }); if (!file) { throw new Error('Offline file not found'); diff --git a/src/context/offline-drive/files/domain/OfflineFile.ts b/src/context/offline-drive/files/domain/OfflineFile.ts index b24934821..906a2605e 100644 --- a/src/context/offline-drive/files/domain/OfflineFile.ts +++ b/src/context/offline-drive/files/domain/OfflineFile.ts @@ -20,21 +20,23 @@ export class OfflineFile extends AggregateRoot { super(); } - public get id(): string { - return this._id.value; + public get id() { + return this._id; } - public get createdAt(): Date { + public get createdAt() { return this._createdAt; } public get path() { - return this._path.value; + return this._path; } public get size() { - return this._size.value; + return this._size; } + public get name() { return this._path.name(); } + public get extension() { return this._path.extension(); } @@ -58,17 +60,15 @@ export class OfflineFile extends AggregateRoot { } increaseSizeBy(bytes: number): void { - const newSize = this.size + bytes; - - this._size = new OfflineFileSize(newSize); + this._size = this._size.increment(bytes); } attributes(): OfflineFileAttributes { return { - id: this.id, - createdAt: this.createdAt, - path: this.path, - size: this.size, + id: this._id.value, + createdAt: this._createdAt, + path: this._path.value, + size: this._size.value, }; } } diff --git a/src/context/offline-drive/files/domain/OfflineFileRepository.ts b/src/context/offline-drive/files/domain/OfflineFileRepository.ts index 7fc8b4ace..ccfdd8ec1 100644 --- a/src/context/offline-drive/files/domain/OfflineFileRepository.ts +++ b/src/context/offline-drive/files/domain/OfflineFileRepository.ts @@ -7,5 +7,5 @@ export interface OfflineFileRepository { partial: Partial ): Promise; - delete(id: OfflineFileAttributes['id']): Promise; + delete(id: OfflineFile['id']): Promise; } diff --git a/src/context/offline-drive/files/domain/OfflineFileSize.ts b/src/context/offline-drive/files/domain/OfflineFileSize.ts index 40397925f..7446410e9 100644 --- a/src/context/offline-drive/files/domain/OfflineFileSize.ts +++ b/src/context/offline-drive/files/domain/OfflineFileSize.ts @@ -1,3 +1,7 @@ import { BucketEntry } from '../../../shared/domain/value-objects/BucketEntry'; -export class OfflineFileSize extends BucketEntry {} +export class OfflineFileSize extends BucketEntry { + increment(bytes: number): OfflineFileSize { + return new OfflineFileSize(this.value + bytes); + } +} diff --git a/src/context/offline-drive/files/infrastructure/InMemoryOfflineFileRepository.ts b/src/context/offline-drive/files/infrastructure/InMemoryOfflineFileRepository.ts index 990646496..88d15cd37 100644 --- a/src/context/offline-drive/files/infrastructure/InMemoryOfflineFileRepository.ts +++ b/src/context/offline-drive/files/infrastructure/InMemoryOfflineFileRepository.ts @@ -5,7 +5,7 @@ export class InMemoryOfflineFileRepository implements OfflineFileRepository { private readonly files = new Map(); async save(file: OfflineFile): Promise { - this.files.set(file.id, file.attributes()); + this.files.set(file.id.value, file.attributes()); } async searchByPartial( @@ -30,7 +30,7 @@ export class InMemoryOfflineFileRepository implements OfflineFileRepository { return OfflineFile.from(file); } - async delete(id: OfflineFileAttributes['id']): Promise { - this.files.delete(id); + async delete(id: OfflineFile['id']): Promise { + this.files.delete(id.value); } } diff --git a/src/context/shared/domain/DownloadProgressTracker.ts b/src/context/shared/domain/DownloadProgressTracker.ts new file mode 100644 index 000000000..17b4ae300 --- /dev/null +++ b/src/context/shared/domain/DownloadProgressTracker.ts @@ -0,0 +1,6 @@ +export interface DownloadProgressTracker { + downloadStarted(name: string): Promise; + downloadUpdate(name: string, progress: number): Promise; + downloadFinished(name: string): Promise; + error(name: string): Promise; +} diff --git a/src/context/shared/domain/value-objects/DateValueObject.ts b/src/context/shared/domain/value-objects/DateValueObject.ts index 6f6efb2de..704e95f4d 100644 --- a/src/context/shared/domain/value-objects/DateValueObject.ts +++ b/src/context/shared/domain/value-objects/DateValueObject.ts @@ -13,6 +13,25 @@ export class DateValueObject extends ValueObject { return new DateValueObject(date); } + isPrevious(than: Date): boolean { + return this.value < than; + } + + isAfter(than: Date): boolean { + return this.value > than; + } + + same(other: Date): boolean { + return this.value.getTime() === other.getTime(); + } + + equals(other: DateValueObject): boolean { + return ( + other.constructor.name === this.constructor.name && + other.value.getTime() === this.value.getTime() + ); + } + toISOString(): string { return this.value.toISOString(); } diff --git a/src/context/virtual-drive/boundaryBridge/application/FileCreationOrchestrator.ts b/src/context/virtual-drive/boundaryBridge/application/FileCreationOrchestrator.ts index 98a033698..f39291c51 100644 --- a/src/context/virtual-drive/boundaryBridge/application/FileCreationOrchestrator.ts +++ b/src/context/virtual-drive/boundaryBridge/application/FileCreationOrchestrator.ts @@ -25,7 +25,7 @@ export class FileCreationOrchestrator { const fileContents = await this.contentsUploader.run(posixRelativePath); const createdFile = await this.fileCreator.run( - path.value, + path, fileContents.id, fileContents.size ); diff --git a/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts b/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts index 0c091b32b..d94336df5 100644 --- a/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts +++ b/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts @@ -1,51 +1,52 @@ -import { broadcastToWindows } from '../../../../apps/main/windows'; +import { DownloadProgressTracker } from '../../../shared/domain/DownloadProgressTracker'; import { File } from '../../files/domain/File'; import { EventBus } from '../../shared/domain/EventBus'; +import { ContentsId } from '../domain/ContentsId'; import { ContentsManagersFactory } from '../domain/ContentsManagersFactory'; import { LocalFileContents } from '../domain/LocalFileContents'; import { LocalFileSystem } from '../domain/LocalFileSystem'; import { ContentFileDownloader } from '../domain/contentHandlers/ContentFileDownloader'; +import Logger from 'electron-log'; export class DownloadContentsToPlainFile { constructor( private readonly managerFactory: ContentsManagersFactory, - private readonly localWriter: LocalFileSystem, - private readonly eventBus: EventBus + private readonly local: LocalFileSystem, + private readonly eventBus: EventBus, + private readonly tracker: DownloadProgressTracker ) {} private async registerEvents(downloader: ContentFileDownloader, file: File) { downloader.on('start', () => { - broadcastToWindows('sync-info-update', { - action: 'DOWNLOADING', - name: file.nameWithExtension, - progress: 0, - }); + this.tracker.downloadStarted(file.nameWithExtension); }); downloader.on('progress', (progress: number) => { - broadcastToWindows('sync-info-update', { - action: 'DOWNLOADING', - name: file.nameWithExtension, - progress: progress, - }); + this.tracker.downloadUpdate(file.nameWithExtension, progress); }); downloader.on('error', () => { - broadcastToWindows('sync-info-update', { - action: 'DOWNLOAD_ERROR', - name: file.nameWithExtension, - }); + this.tracker.error(file.nameWithExtension); }); downloader.on('finish', () => { - broadcastToWindows('sync-info-update', { - action: 'DOWNLOADED', - name: file.nameWithExtension, - }); + this.tracker.downloadFinished(file.nameWithExtension); }); } - async run(file: File): Promise { + async run(file: File): Promise { + const contentsId = new ContentsId(file.contentsId); + + const metadata = await this.local.metadata(contentsId); + + if (metadata) { + if (metadata.isUpToDate(file.updatedAt)) { + return; + } + } + + Logger.debug(`downloading "${file.nameWithExtension}"`); + const downloader = this.managerFactory.downloader(); this.registerEvents(downloader, file); @@ -57,11 +58,9 @@ export class DownloadContentsToPlainFile { downloader.elapsedTime() ); - const write = await this.localWriter.write(localContents, file.contentsId); + await this.local.write(localContents, file.contentsId); const events = localContents.pullDomainEvents(); await this.eventBus.publish(events); - - return write; } } diff --git a/src/context/virtual-drive/contents/application/LocalContentsMover.ts b/src/context/virtual-drive/contents/application/LocalContentsMover.ts index b78d9febe..ebc267591 100644 --- a/src/context/virtual-drive/contents/application/LocalContentsMover.ts +++ b/src/context/virtual-drive/contents/application/LocalContentsMover.ts @@ -6,8 +6,6 @@ export class LocalContentsMover { constructor(private readonly fileSystem: LocalFileSystem) {} async run(contentsId: ContentsId, src: string): Promise { - Logger.debug('LocalContentsMover contentsId', contentsId, 'src', src); - const exists = await this.fileSystem.exists(contentsId); if (exists) { @@ -15,5 +13,7 @@ export class LocalContentsMover { } await this.fileSystem.add(contentsId, src); + + Logger.info('Added', contentsId.value, 'to offline files contents cache'); } } diff --git a/src/context/virtual-drive/contents/domain/ContentsMetadata.ts b/src/context/virtual-drive/contents/domain/ContentsMetadata.ts new file mode 100644 index 000000000..16e51005f --- /dev/null +++ b/src/context/virtual-drive/contents/domain/ContentsMetadata.ts @@ -0,0 +1,30 @@ +import { AggregateRoot } from '../../../shared/domain/AggregateRoot'; +import { DateValueObject } from '../../../shared/domain/value-objects/DateValueObject'; + +type ContentsMetadataAttributes = { + modificationDate: Date; +}; + +export class ContentsMetadata extends AggregateRoot { + private constructor(readonly modificationDate: DateValueObject) { + super(); + } + + static from(attributes: ContentsMetadataAttributes): ContentsMetadata { + return new ContentsMetadata( + new DateValueObject(attributes.modificationDate) + ); + } + + attributes(): ContentsMetadataAttributes { + return { + modificationDate: this.modificationDate.value, + }; + } + + isUpToDate(date: Date): boolean { + return ( + this.modificationDate.same(date) || this.modificationDate.isAfter(date) + ); + } +} diff --git a/src/context/virtual-drive/contents/domain/LocalFileSystem.ts b/src/context/virtual-drive/contents/domain/LocalFileSystem.ts index 4fc8ece13..73eb7b6ce 100644 --- a/src/context/virtual-drive/contents/domain/LocalFileSystem.ts +++ b/src/context/virtual-drive/contents/domain/LocalFileSystem.ts @@ -1,3 +1,4 @@ +import { ContentsMetadata } from './ContentsMetadata'; import { ContentsId } from './ContentsId'; import { LocalFileContents } from './LocalFileContents'; @@ -10,6 +11,11 @@ export interface LocalFileSystem { exists(contentsId: ContentsId): Promise; + /** + * @return the FileMetadata of the file or undefined if does not exits + */ + metadata(contentsId: ContentsId): Promise; + add(contentsId: ContentsId, source: string): Promise; listExistentFiles(): Promise>; diff --git a/src/context/virtual-drive/contents/infrastructure/FSLocalFileSystem.ts b/src/context/virtual-drive/contents/infrastructure/FSLocalFileSystem.ts index 24ff100f2..6dceecc58 100644 --- a/src/context/virtual-drive/contents/infrastructure/FSLocalFileSystem.ts +++ b/src/context/virtual-drive/contents/infrastructure/FSLocalFileSystem.ts @@ -6,6 +6,7 @@ import { LocalFileSystem } from '../domain/LocalFileSystem'; import path from 'path'; import fs from 'fs/promises'; import { ContentsId } from '../domain/ContentsId'; +import { ContentsMetadata } from '../domain/ContentsMetadata'; export class FSLocalFileSystem implements LocalFileSystem { constructor( @@ -52,6 +53,16 @@ export class FSLocalFileSystem implements LocalFileSystem { } } + async metadata(contentsId: ContentsId) { + const folder = await this.baseFolder(); + + const absolutePath = path.join(folder, contentsId.value); + + const { mtimeMs } = await fs.stat(absolutePath); + + return ContentsMetadata.from({ modificationDate: new Date(mtimeMs) }); + } + async add(contentsId: ContentsId, source: string): Promise { const folder = await this.baseFolder(); diff --git a/src/context/virtual-drive/files/application/CreateFileOnOfflineFileUplodaded.ts b/src/context/virtual-drive/files/application/CreateFileOnOfflineFileUplodaded.ts index b347ca8cc..87b4457c8 100644 --- a/src/context/virtual-drive/files/application/CreateFileOnOfflineFileUplodaded.ts +++ b/src/context/virtual-drive/files/application/CreateFileOnOfflineFileUplodaded.ts @@ -1,6 +1,7 @@ import { OfflineContentsUploadedDomainEvent } from '../../../offline-drive/contents/domain/events/OfflineContentsUploadedDomainEvent'; import { DomainEventClass } from '../../../shared/domain/DomainEvent'; import { DomainEventSubscriber } from '../../../shared/domain/DomainEventSubscriber'; +import { FilePath } from '../domain/FilePath'; import { FileCreator } from './FileCreator'; export class CreateFileOnOfflineFileUploaded @@ -13,6 +14,8 @@ export class CreateFileOnOfflineFileUploaded } async on(event: OfflineContentsUploadedDomainEvent): Promise { - await this.creator.run(event.path, event.aggregateId, event.size); + const filePath = new FilePath(event.path); + + await this.creator.run(filePath, event.aggregateId, event.size); } } diff --git a/src/context/virtual-drive/files/application/FileCreator.ts b/src/context/virtual-drive/files/application/FileCreator.ts index cde683228..a6e236895 100644 --- a/src/context/virtual-drive/files/application/FileCreator.ts +++ b/src/context/virtual-drive/files/application/FileCreator.ts @@ -21,10 +21,12 @@ export class FileCreator { private readonly notifier: SyncFileMessenger ) {} - async run(path: string, contentsId: string, size: number): Promise { + async run( + filePath: FilePath, + contentsId: string, + size: number + ): Promise { try { - const filePath = new FilePath(path); - const existingFile = this.repository.searchByPartial({ path: PlatformPathConverter.winToPosix(filePath.value), status: FileStatuses.EXISTS, @@ -58,8 +60,6 @@ export class FileCreator { const message = error instanceof Error ? error.message : '[File Creator] unknown error'; - const filePath = new FilePath(path); - await this.notifier.errorWhileCreating( filePath.name(), filePath.extension(), diff --git a/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts b/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts index 6208baced..f6ff744b4 100644 --- a/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts +++ b/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts @@ -27,16 +27,24 @@ export class SDKRemoteFileSystem implements RemoteFileSystem { throw new Error('Failed to encrypt name'); } - const data = await this.sdk.createFileEntry({ - id: offline.contentsId, - type: offline.type, - size: offline.size, - name: encryptedName, - plain_name: offline.name, - bucket: this.bucket, - folder_id: offline.folderId, - encrypt_version: EncryptionVersion.Aes03, - }); + const body = { + file: { + fileId: offline.contentsId, + file_id: offline.contentsId, + type: offline.type, + size: offline.size, + name: encryptedName, + plain_name: offline.name, + bucket: this.bucket, + folder_id: offline.folderId, + encrypt_version: EncryptionVersion.Aes03, + }, + }; + + const { data } = await this.clients.drive.post( + `${process.env.API_URL}/api/storage/file`, + body + ); return { ...data, diff --git a/tests/context/offline-drive/files/__mocks__/OfflineFileRepositoryMock.ts b/tests/context/offline-drive/files/__mocks__/OfflineFileRepositoryMock.ts index 41e279dcf..f33fa6350 100644 --- a/tests/context/offline-drive/files/__mocks__/OfflineFileRepositoryMock.ts +++ b/tests/context/offline-drive/files/__mocks__/OfflineFileRepositoryMock.ts @@ -2,6 +2,7 @@ import { OfflineFile, OfflineFileAttributes, } from '../../../../../src/context/offline-drive/files/domain/OfflineFile'; +import { OfflineFileId } from '../../../../../src/context/offline-drive/files/domain/OfflineFileId'; import { OfflineFileRepository } from '../../../../../src/context/offline-drive/files/domain/OfflineFileRepository'; export class OfflineFileRepositoryMock implements OfflineFileRepository { @@ -17,7 +18,8 @@ export class OfflineFileRepositoryMock implements OfflineFileRepository { ): Promise { return this.searchByPartialMock(partial); } - delete(id: string): Promise { + + delete(id: OfflineFileId): Promise { return this.deleteMock(id); } } diff --git a/tests/context/offline-drive/files/application/OfflineFileSizeIncreaser.test.ts b/tests/context/offline-drive/files/application/OfflineFileSizeIncreaser.test.ts index 577d69981..c8af7d93e 100644 --- a/tests/context/offline-drive/files/application/OfflineFileSizeIncreaser.test.ts +++ b/tests/context/offline-drive/files/application/OfflineFileSizeIncreaser.test.ts @@ -1,6 +1,5 @@ import { OfflineFileSizeIncreaser } from '../../../../../src/context/offline-drive/files/application/OfflineFileSizeIncreaser'; import { OfflineFile } from '../../../../../src/context/offline-drive/files/domain/OfflineFile'; -import { FileSize } from '../../../../../src/context/virtual-drive/files/domain/FileSize'; import { OfflineFileRepositoryMock } from '../__mocks__/OfflineFileRepositoryMock'; import { OfflineFileIdMother } from '../domain/OfflineFileIdMother'; import { OfflineFileMother } from '../domain/OfflineFileMother'; @@ -26,10 +25,10 @@ describe('Offline File Size Increaser', () => { OfflineFile.from(file.attributes()) ); - await SUT.run(id.value, amount); + await SUT.run(id, amount); expect(repository.saveMock).toBeCalledWith( - expect.objectContaining({ _size: new FileSize(file.size + amount) }) + expect.objectContaining({ _size: file.size.increment(amount) }) ); }); }); diff --git a/tests/context/shared/domain/DateMother.ts b/tests/context/shared/domain/DateMother.ts new file mode 100644 index 000000000..34df55d70 --- /dev/null +++ b/tests/context/shared/domain/DateMother.ts @@ -0,0 +1,25 @@ +export class DateMother { + static previousNDay(original: Date, n: number): Date { + const clone = new Date(original.toDateString()); + + clone.setDate(original.getDate() - n); + + return clone; + } + + static previousDay = (original: Date) => DateMother.previousNDay(original, 1); + + static nextNDay(original: Date, n: number): Date { + const clone = new Date(original.toDateString()); + + clone.setDate(original.getDate() + n); + + return clone; + } + + static nextDay = (original: Date) => DateMother.nextNDay(original, 1); + + static clone(original: Date): Date { + return new Date(original.getTime()); + } +} diff --git a/tests/context/shared/domain/value-objects/DateValueObject.test.ts b/tests/context/shared/domain/value-objects/DateValueObject.test.ts new file mode 100644 index 000000000..53808280e --- /dev/null +++ b/tests/context/shared/domain/value-objects/DateValueObject.test.ts @@ -0,0 +1,78 @@ +import { DateValueObject } from '../../../../../src/context/shared/domain/value-objects/DateValueObject'; +import MockDate from 'mockdate'; + +describe('Date Value Object', () => { + describe('creation', () => { + it('creates DateValueObject from string', () => { + const dateString = '2024-01-30T12:34:56.789Z'; + const dateValueObject = DateValueObject.fromString(dateString); + + expect(dateValueObject).toBeInstanceOf(DateValueObject); + expect(dateValueObject.value.toISOString()).toBe(dateString); + }); + + it('creates DateValueObject with current date', () => { + const currentDate = new Date(); + + MockDate.set(currentDate); + + const dateValueObject = DateValueObject.now(); + + expect(dateValueObject).toBeInstanceOf(DateValueObject); + expect(dateValueObject.same(currentDate)).toBeTruthy(); + }); + }); + + describe('equality', () => { + it('correctly checks if the date is the same', () => { + const date = new Date('2024-01-30T12:34:56.000Z'); + const sameDate = new Date('2024-01-30T12:34:56.000Z'); + const differentDate = new Date('2000-01-30T12:34:57.000Z'); + + const dateValueObject = new DateValueObject(date); + + expect(dateValueObject.same(sameDate)).toBeTruthy(); + expect(dateValueObject.same(differentDate)).toBeFalsy(); + }); + + it('correctly checks if is equals to another date VO', () => { + const date = new Date('2024-01-30T12:34:56.000Z'); + const sameDate = new Date('2024-01-30T12:34:56.000Z'); + const differentDate = new Date('2000-01-30T12:34:57.000Z'); + + const dateValueObject = new DateValueObject(date); + const sameValueObject = new DateValueObject(sameDate); + const differentValueObject = new DateValueObject(differentDate); + + expect(dateValueObject.equals(sameValueObject)).toBeTruthy(); + expect(dateValueObject.equals(differentValueObject)).toBeFalsy(); + }); + }); + + describe('comparison', () => { + it('correctly checks if the date is previous', () => { + const earlierDate = new Date('2023-01-30T12:34:56.000Z'); + const laterDate = new Date('2024-01-30T12:34:57.000Z'); + const dateValueObject = new DateValueObject(earlierDate); + + expect(dateValueObject.isPrevious(laterDate)).toBeTruthy(); + expect(dateValueObject.isPrevious(earlierDate)).toBeFalsy(); + }); + + it('correctly checks if the date is after', () => { + const earlierDate = new Date('2023-01-30T12:34:56.000Z'); + const laterDate = new Date('2024-01-30T12:34:57.000Z'); + const dateValueObject = new DateValueObject(laterDate); + + expect(dateValueObject.isAfter(earlierDate)).toBeTruthy(); + expect(dateValueObject.isAfter(laterDate)).toBeFalsy(); + }); + }); + + it('correctly converts to ISO string', () => { + const dateString = '2024-01-30T12:34:56.789Z'; + const dateValueObject = new DateValueObject(new Date(dateString)); + + expect(dateValueObject.toISOString()).toBe(dateString); + }); +}); diff --git a/tests/context/virtual-drive/contents/__mocks__/ContentFileDownloaderMock.ts b/tests/context/virtual-drive/contents/__mocks__/ContentFileDownloaderMock.ts index 5fa88c343..59a2f125f 100644 --- a/tests/context/virtual-drive/contents/__mocks__/ContentFileDownloaderMock.ts +++ b/tests/context/virtual-drive/contents/__mocks__/ContentFileDownloaderMock.ts @@ -5,12 +5,12 @@ import { } from '../../../../../src/context/virtual-drive/contents/domain/contentHandlers/ContentFileDownloader'; export class ContentFileDownloaderMock implements ContentFileDownloader { - mock = jest.fn(); + downloadMock = jest.fn(); onMock = jest.fn(); elapsedTimeMock = jest.fn(); download(): Promise { - return this.mock(); + return this.downloadMock(); } forceStop(): void { diff --git a/tests/context/virtual-drive/contents/__mocks__/LocalFileWriterMock.ts b/tests/context/virtual-drive/contents/__mocks__/LocalFileWriterMock.ts index 3fb24d4a8..34890bcbd 100644 --- a/tests/context/virtual-drive/contents/__mocks__/LocalFileWriterMock.ts +++ b/tests/context/virtual-drive/contents/__mocks__/LocalFileWriterMock.ts @@ -6,6 +6,7 @@ export class LocalFileSystemMock implements LocalFileSystem { public writeMock = jest.fn(); public removeMock = jest.fn(); public existsMock = jest.fn(); + public metadataMock = jest.fn(); public addMock = jest.fn(); public listExistentFilesMock = jest.fn(); @@ -21,6 +22,10 @@ export class LocalFileSystemMock implements LocalFileSystem { return this.existsMock(contentId); } + metadata(contentsId: ContentsId) { + return this.metadataMock(contentsId); + } + add(contentsId: ContentsId, source: string): Promise { return this.addMock(contentsId, source); } diff --git a/tests/context/virtual-drive/contents/application/ContentsDownloader._test.ts b/tests/context/virtual-drive/contents/application/ContentsDownloader._test.ts index fbad10b7d..c7c3fafb1 100644 --- a/tests/context/virtual-drive/contents/application/ContentsDownloader._test.ts +++ b/tests/context/virtual-drive/contents/application/ContentsDownloader._test.ts @@ -39,7 +39,7 @@ describe.skip('Contents Downloader', () => { >)( 'tracks all the manager events ', async (event: keyof FileDownloadEvents) => { - factory.mockDownloader.mock.mockResolvedValueOnce( + factory.mockDownloader.downloadMock.mockResolvedValueOnce( new ReadableHelloWorld() ); @@ -56,7 +56,9 @@ describe.skip('Contents Downloader', () => { ); it('writes the downloaded content a local file', async () => { - factory.mockDownloader.mock.mockResolvedValueOnce(new ReadableHelloWorld()); + factory.mockDownloader.downloadMock.mockResolvedValueOnce( + new ReadableHelloWorld() + ); const file = FileMother.any(); await SUT.run(file, async () => { diff --git a/tests/context/virtual-drive/contents/application/DownloadContentsToPlainFile.test.ts b/tests/context/virtual-drive/contents/application/DownloadContentsToPlainFile.test.ts new file mode 100644 index 000000000..ec7266322 --- /dev/null +++ b/tests/context/virtual-drive/contents/application/DownloadContentsToPlainFile.test.ts @@ -0,0 +1,74 @@ +import { DownloadContentsToPlainFile } from '../../../../../src/context/virtual-drive/contents/application/DownloadContentsToPlainFile'; +import { ContentsMetadata } from '../../../../../src/context/virtual-drive/contents/domain/ContentsMetadata'; +import { DateMother } from '../../../shared/domain/DateMother'; +import { FileMother } from '../../files/domain/FileMother'; +import { DownloadProgressTrackerMock } from '../../shared/__mock__/DownloadProgressTrackerMock'; +import { EventBusMock } from '../../shared/__mock__/EventBusMock'; +import { LocalFileSystemMock } from '../__mocks__/LocalFileWriterMock'; +import { RemoteFileContentsManagersFactoryMock } from '../__mocks__/RemoteFileContentsManagersFactoryMock'; + +describe('Download Contents To Plain File', () => { + let managerFactory: RemoteFileContentsManagersFactoryMock; + let local: LocalFileSystemMock; + let eventBus: EventBusMock; + let tracker: DownloadProgressTrackerMock; + + let SUT: DownloadContentsToPlainFile; + + beforeEach(() => { + managerFactory = new RemoteFileContentsManagersFactoryMock(); + local = new LocalFileSystemMock(); + eventBus = new EventBusMock(); + tracker = new DownloadProgressTrackerMock(); + + SUT = new DownloadContentsToPlainFile( + managerFactory, + local, + eventBus, + tracker + ); + }); + + describe('cache strategy', () => { + it('downloads the file if not exits on local', async () => { + const file = FileMother.any(); + + local.metadataMock.mockResolvedValueOnce(undefined); + + await SUT.run(file); + + expect(managerFactory.mockDownloader.downloadMock).toBeCalled(); + expect(local.writeMock).toBeCalled(); + }); + + it('downloads the file if exists on local but its not up to date', async () => { + const file = FileMother.any(); + + local.metadataMock.mockResolvedValueOnce( + ContentsMetadata.from({ + modificationDate: DateMother.previousDay(file.updatedAt), + }) + ); + + await SUT.run(file); + + expect(managerFactory.mockDownloader.downloadMock).toBeCalled(); + expect(local.writeMock).toBeCalled(); + }); + + it('does not download the file if exists on local and its up to date', async () => { + const file = FileMother.any(); + + local.metadataMock.mockResolvedValueOnce( + ContentsMetadata.from({ + modificationDate: file.updatedAt, + }) + ); + + await SUT.run(file); + + expect(managerFactory.mockDownloader.downloadMock).not.toBeCalled(); + expect(local.writeMock).not.toBeCalled(); + }); + }); +}); diff --git a/tests/context/virtual-drive/contents/domain/ContentsMetadata.test.ts b/tests/context/virtual-drive/contents/domain/ContentsMetadata.test.ts new file mode 100644 index 000000000..504b0a361 --- /dev/null +++ b/tests/context/virtual-drive/contents/domain/ContentsMetadata.test.ts @@ -0,0 +1,17 @@ +import { ContentsMetadata } from '../../../../../src/context/virtual-drive/contents/domain/ContentsMetadata'; +import { DateMother } from '../../../shared/domain/DateMother'; + +describe('Contents Metadata', () => { + it('is up to date when the modification date is the same as or later than the given date', () => { + const date = new Date(); + const sameDate = DateMother.clone(date); + const previousDayDate = DateMother.previousDay(date); + const nextDayDate = DateMother.nextDay(date); + + const metadata = ContentsMetadata.from({ modificationDate: date }); + + expect(metadata.isUpToDate(sameDate)).toBeTruthy(); + expect(metadata.isUpToDate(previousDayDate)).toBeTruthy(); + expect(metadata.isUpToDate(nextDayDate)).toBeFalsy(); + }); +}); diff --git a/tests/context/virtual-drive/files/application/FileCreator.test.ts b/tests/context/virtual-drive/files/application/FileCreator.test.ts index 3076ef382..720122295 100644 --- a/tests/context/virtual-drive/files/application/FileCreator.test.ts +++ b/tests/context/virtual-drive/files/application/FileCreator.test.ts @@ -53,7 +53,7 @@ describe('File Creator', () => { remoteFileSystemMock.persistMock.mockResolvedValueOnce(fileAttributes); - await SUT.run(path.value, contents.id, contents.size); + await SUT.run(path, contents.id, contents.size); expect(fileRepository.addMock).toBeCalledWith( expect.objectContaining({ @@ -76,7 +76,7 @@ describe('File Creator', () => { remoteFileSystemMock.persistMock.mockResolvedValueOnce(fileAttributes); - await SUT.run(path.value, contents.id, contents.size); + await SUT.run(path, contents.id, contents.size); expect(eventBus.publishMock.mock.calls[0][0][0].eventName).toBe( 'file.created' @@ -109,7 +109,7 @@ describe('File Creator', () => { // returns Promise }); - await SUT.run(path.value, contents.id, contents.size); + await SUT.run(path, contents.id, contents.size); expect(deleterSpy).toBeCalledWith(existingFile.contentsId); diff --git a/tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts b/tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts new file mode 100644 index 000000000..c6fd7f983 --- /dev/null +++ b/tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts @@ -0,0 +1,21 @@ +import { DownloadProgressTracker } from '../../../../../src/context/shared/domain/DownloadProgressTracker'; + +export class DownloadProgressTrackerMock implements DownloadProgressTracker { + readonly downloadStartedMock = jest.fn(); + readonly downloadUpdateMock = jest.fn(); + readonly downloadFinishedMock = jest.fn(); + readonly errorMock = jest.fn(); + + downloadStarted(name: string): Promise { + return this.downloadStartedMock(name); + } + downloadUpdate(name: string, progress: number): Promise { + return this.downloadUpdateMock(name, progress); + } + downloadFinished(name: string): Promise { + return this.downloadFinishedMock(name); + } + error(name: string): Promise { + return this.errorMock(name); + } +} From efd8670c85aa2c086b8f13df766dfad34a7663a9 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 31 Jan 2024 13:13:32 +0100 Subject: [PATCH 03/15] chore: add deps --- package.json | 1 + yarn.lock | 87 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 5a5625b14..c804db203 100644 --- a/package.json +++ b/package.json @@ -229,6 +229,7 @@ "jsonwebtoken": "^8.5.1", "lint-staged": "^12.1.4", "mini-css-extract-plugin": "^2.4.5", + "mockdate": "^3.0.5", "ms": "^2.1.3", "node-loader": "^2.0.0", "opencollective-postinstall": "^2.0.3", diff --git a/yarn.lock b/yarn.lock index f141ec13a..a09e3384c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1520,6 +1520,13 @@ dependencies: "@sinclair/typebox" "^0.25.16" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -1593,6 +1600,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" @@ -2165,6 +2184,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -7895,7 +7919,7 @@ jest-snapshot@^27.5.1: pretty-format "^27.5.1" semver "^7.3.2" -jest-util@^27.0.0, jest-util@^27.5.1: +jest-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== @@ -7907,6 +7931,18 @@ jest-util@^27.0.0, jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-util@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" @@ -8070,11 +8106,6 @@ json-stringify-safe@^5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@2.x, json5@^2.1.2, json5@^2.2.0, json5@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - json5@^1.0.1, json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -8082,6 +8113,11 @@ json5@^1.0.1, json5@^1.0.2: dependencies: minimist "^1.2.0" +json5@^2.1.2, json5@^2.2.0, json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -8844,6 +8880,11 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mockdate@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" + integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ== + mrmime@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" @@ -10547,13 +10588,6 @@ semver@7.3.8: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - semver@^5.6.0, semver@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" @@ -10564,6 +10598,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -11434,19 +11475,19 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -ts-jest@^27.1.2: - version "27.1.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" - integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== +ts-jest@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" + integrity sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" - jest-util "^27.0.0" - json5 "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" lodash.memoize "4.x" make-error "1.x" - semver "7.x" - yargs-parser "20.x" + semver "^7.5.3" + yargs-parser "^21.0.1" ts-loader@^9.2.6: version "9.4.3" @@ -12227,12 +12268,12 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.9: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.1.1: +yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== From 4762fe106790d356fd10843d3413580d20db2f7a Mon Sep 17 00:00:00 2001 From: joan vicens Date: Wed, 31 Jan 2024 13:14:37 +0100 Subject: [PATCH 04/15] chore: readd fuse --- release/app/package.json | 1 + release/app/yarn.lock | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/release/app/package.json b/release/app/package.json index 36761c25b..c0cf7e5f5 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -10,6 +10,7 @@ "postinstall": "npm run electron-rebuild --sequential && npm run link-modules" }, "dependencies": { + "@gcas/fuse": "^2.4.2", "@rudderstack/rudder-sdk-node": "^1.1.4", "better-sqlite3": "^8.3.0", "typeorm": "^0.3.16" diff --git a/release/app/yarn.lock b/release/app/yarn.lock index ebb2bf8c9..abc41fb32 100644 --- a/release/app/yarn.lock +++ b/release/app/yarn.lock @@ -30,6 +30,14 @@ enabled "2.0.x" kuler "^2.0.0" +"@gcas/fuse@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@gcas/fuse/-/fuse-2.4.2.tgz#149bd97ec8a60988f4868bacd719c7e8a1c41876" + integrity sha512-l/vVd2eXAuzKG4QilN1VRa8za5glndSK+jxcLrzMiXRrvbbCJthwqcBZUE3VzoRL0T/l7197tW+MKR9YeQCtZQ== + dependencies: + nanoresource "^1.3.0" + napi-macros "^2.0.0" + "@ioredis/commands@^1.1.1": version "1.2.0" resolved "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz" @@ -664,11 +672,23 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nanoresource@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/nanoresource/-/nanoresource-1.3.0.tgz#823945d9667ab3e81a8b2591ab8d734552878cd0" + integrity sha512-OI5dswqipmlYfyL3k/YMm7mbERlh4Bd1KuKdMHpeoVD1iVxqxaTMKleB4qaA2mbQZ6/zMNSxCXv9M9P/YbqTuQ== + dependencies: + inherits "^2.0.4" + napi-build-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== +napi-macros@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.2.2.tgz#817fef20c3e0e40a963fbf7b37d1600bd0201044" + integrity sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g== + node-abi@^3.3.0: version "3.51.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.51.0.tgz#970bf595ef5a26a271307f8a4befa02823d4e87d" From 8a8daea33e3400ced46fcf0bf56f03e122899e9e Mon Sep 17 00:00:00 2001 From: joan vicens Date: Thu, 1 Feb 2024 11:44:04 +0100 Subject: [PATCH 05/15] feat: Main Process Download Progress Tracker --- .../virtual-drive/contents/builder.ts | 6 +- .../dependency-injection/contents/builder.ts | 6 +- .../shared/domain/DownloadProgressTracker.ts | 23 ++++- .../MainProcessDownloadProgressTracker.ts | 93 +++++++++++++++++++ .../DownloadContentsToPlainFile.ts | 17 ++-- .../contentHandlers/ContentFileDownloader.ts | 2 +- .../EnvironmentContentFileDownloader.ts | 4 +- .../__mock__/DownloadProgressTrackerMock.ts | 33 +++++-- 8 files changed, 161 insertions(+), 23 deletions(-) create mode 100644 src/context/shared/infrastructure/MainProcessDownloadProgressTracker.ts diff --git a/src/apps/fuse/dependency-injection/virtual-drive/contents/builder.ts b/src/apps/fuse/dependency-injection/virtual-drive/contents/builder.ts index d18ee1ff5..b6fb13a9e 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/contents/builder.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/contents/builder.ts @@ -16,6 +16,7 @@ import { MoveOfflineContentsOnContentsUploaded } from '../../../../../context/vi import { LocalContentsMover } from '../../../../../context/virtual-drive/contents/application/LocalContentsMover'; import { AllLocalContentsDeleter } from '../../../../../context/virtual-drive/contents/application/AllLocalContentsDeleter'; import { MainProcessUploadProgressTracker } from '../../../../../context/shared/infrastructure/MainProcessUploadProgressTracker'; +import { MainProcessDownloadProgressTracker } from '../../../../../context/shared/infrastructure/MainProcessDownloadProgressTracker'; export async function buildContentsContainer( sharedContainer: SharedContainer @@ -44,10 +45,13 @@ export async function buildContentsContainer( 'downloaded' ); + const tracker = new MainProcessDownloadProgressTracker(); + const downloadContentsToPlainFile = new DownloadContentsToPlainFile( contentsManagerFactory, localFS, - eventBus + eventBus, + tracker ); const localContentChecker = new LocalContentChecker(localFS); diff --git a/src/apps/hydration-api/dependency-injection/contents/builder.ts b/src/apps/hydration-api/dependency-injection/contents/builder.ts index 32e6826c9..8876a20e3 100644 --- a/src/apps/hydration-api/dependency-injection/contents/builder.ts +++ b/src/apps/hydration-api/dependency-injection/contents/builder.ts @@ -8,6 +8,7 @@ import { FSLocalFileSystem } from '../../../../context/virtual-drive/contents/in import { DependencyInjectionEventBus } from '../common/eventBus'; import { FuseAppDataLocalFileContentsDirectoryProvider } from '../../../../context/virtual-drive/shared/infrastructure/LocalFileContentsDirectoryProviders/FuseAppDataLocalFileContentsDirectoryProvider'; import { LocalContentsDeleter } from '../../../../context/virtual-drive/contents/application/LocalContentsDeleter'; +import { MainProcessDownloadProgressTracker } from '../../../../context/shared/infrastructure/MainProcessDownloadProgressTracker'; export async function buildContentsContainer(): Promise { const user = DependencyInjectionUserProvider.get(); @@ -32,10 +33,13 @@ export async function buildContentsContainer(): Promise { 'downloaded' ); + const tracker = new MainProcessDownloadProgressTracker(); + const downloadContentsToPlainFile = new DownloadContentsToPlainFile( contentsManagerFactory, localFS, - eventBus + eventBus, + tracker ); const localContentsDeleter = new LocalContentsDeleter(localFS); diff --git a/src/context/shared/domain/DownloadProgressTracker.ts b/src/context/shared/domain/DownloadProgressTracker.ts index 17b4ae300..277a7cd57 100644 --- a/src/context/shared/domain/DownloadProgressTracker.ts +++ b/src/context/shared/domain/DownloadProgressTracker.ts @@ -1,6 +1,21 @@ export interface DownloadProgressTracker { - downloadStarted(name: string): Promise; - downloadUpdate(name: string, progress: number): Promise; - downloadFinished(name: string): Promise; - error(name: string): Promise; + downloadStarted(name: string, extension: string, size: number): Promise; + downloadUpdate( + name: string, + extension: string, + size: number, + progress: { + elapsedTime: number; + percentage: number; + } + ): Promise; + downloadFinished( + name: string, + extension: string, + size: number, + progress: { + elapsedTime: number; + } + ): Promise; + error(name: string, extension: string): Promise; } diff --git a/src/context/shared/infrastructure/MainProcessDownloadProgressTracker.ts b/src/context/shared/infrastructure/MainProcessDownloadProgressTracker.ts new file mode 100644 index 000000000..f3fd7a0e0 --- /dev/null +++ b/src/context/shared/infrastructure/MainProcessDownloadProgressTracker.ts @@ -0,0 +1,93 @@ +import { trackError, trackEvent } from '../../../apps/main/analytics/service'; +import { setTrayStatus } from '../../../apps/main/tray/tray'; +import { broadcastToWindows } from '../../../apps/main/windows'; +import { DownloadProgressTracker } from '../domain/DownloadProgressTracker'; +import { SyncMessenger } from '../domain/SyncMessenger'; + +export class MainProcessDownloadProgressTracker + extends SyncMessenger + implements DownloadProgressTracker +{ + async downloadStarted( + name: string, + extension: string, + size: number + ): Promise { + setTrayStatus('SYNCING'); + + trackEvent('Upload Started', { + file_name: name, + file_extension: extension, + file_size: size, + elapsedTimeMs: 0, + }); + + broadcastToWindows('sync-info-update', { + action: 'DOWNLOADING', + name: this.nameWithExtension(name, extension), + progress: 0, + }); + } + + async downloadUpdate( + name: string, + extension: string, + size: number, + progress: { elapsedTime: number; percentage: number } + ): Promise { + trackEvent('Upload Started', { + file_name: name, + file_extension: extension, + file_size: size, + elapsedTimeMs: progress.elapsedTime, + }); + + broadcastToWindows('sync-info-update', { + action: 'DOWNLOADING', + name: this.nameWithExtension(name, extension), + progress: progress.percentage, + }); + } + + async downloadFinished( + name: string, + extension: string, + size: number, + progress: { + elapsedTime: number; + } + ): Promise { + const nameWithExtension = this.nameWithExtension(name, extension); + + setTrayStatus('IDLE'); + + trackEvent('Upload Completed', { + file_name: name, + file_extension: extension, + file_size: size, + elapsedTimeMs: progress.elapsedTime, + }); + + broadcastToWindows('sync-info-update', { + action: 'UPLOADED', + name: nameWithExtension, + }); + } + + async error(name: string, extension: string): Promise { + const nameWithExtension = this.nameWithExtension(name, extension); + + // TODO: finish this + trackError('Upload Error', new Error(), { + itemType: 'File', + root: '', + from: name, + action: 'Upload', + }); + + broadcastToWindows('sync-info-update', { + action: 'DOWNLOAD_ERROR', + name: nameWithExtension, + }); + } +} diff --git a/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts b/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts index d94336df5..75563363e 100644 --- a/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts +++ b/src/context/virtual-drive/contents/application/DownloadContentsToPlainFile.ts @@ -18,19 +18,24 @@ export class DownloadContentsToPlainFile { private async registerEvents(downloader: ContentFileDownloader, file: File) { downloader.on('start', () => { - this.tracker.downloadStarted(file.nameWithExtension); + this.tracker.downloadStarted(file.name, file.type, file.size); }); - downloader.on('progress', (progress: number) => { - this.tracker.downloadUpdate(file.nameWithExtension, progress); + downloader.on('progress', (progress: number, elapsedTime: number) => { + this.tracker.downloadUpdate(file.name, file.type, file.size, { + elapsedTime, + percentage: progress, + }); }); downloader.on('error', () => { - this.tracker.error(file.nameWithExtension); + this.tracker.error(file.name, file.type); }); - downloader.on('finish', () => { - this.tracker.downloadFinished(file.nameWithExtension); + downloader.on('finish', (elapsedTime: number) => { + this.tracker.downloadFinished(file.name, file.type, file.size, { + elapsedTime, + }); }); } diff --git a/src/context/virtual-drive/contents/domain/contentHandlers/ContentFileDownloader.ts b/src/context/virtual-drive/contents/domain/contentHandlers/ContentFileDownloader.ts index 586057f85..89dcf62ff 100644 --- a/src/context/virtual-drive/contents/domain/contentHandlers/ContentFileDownloader.ts +++ b/src/context/virtual-drive/contents/domain/contentHandlers/ContentFileDownloader.ts @@ -3,7 +3,7 @@ import { File, FileAttributes } from '../../../files/domain/File'; export type FileDownloadEvents = { start: () => void; - progress: (progress: number) => void; + progress: (progress: number, elapsedTime: number) => void; finish: (fileId: FileAttributes['contentsId']) => void; error: (error: Error) => void; }; diff --git a/src/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.ts b/src/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.ts index 5744672bd..d9dea7b72 100644 --- a/src/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.ts +++ b/src/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.ts @@ -44,7 +44,7 @@ export class EnvironmentContentFileDownloader implements ContentFileDownloader { file.contentsId, { progressCallback: (progress: number) => { - this.eventEmitter.emit('progress', progress); + this.eventEmitter.emit('progress', progress, this.elapsedTime()); }, finishedCallback: async (err: Error, stream: Readable) => { this.stopwatch.finish(); @@ -53,7 +53,7 @@ export class EnvironmentContentFileDownloader implements ContentFileDownloader { this.eventEmitter.emit('error', err); return reject(err); } - this.eventEmitter.emit('finish'); + this.eventEmitter.emit('finish', this.elapsedTime()); resolve(stream); }, diff --git a/tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts b/tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts index c6fd7f983..ec43c5397 100644 --- a/tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts +++ b/tests/context/virtual-drive/shared/__mock__/DownloadProgressTrackerMock.ts @@ -6,16 +6,33 @@ export class DownloadProgressTrackerMock implements DownloadProgressTracker { readonly downloadFinishedMock = jest.fn(); readonly errorMock = jest.fn(); - downloadStarted(name: string): Promise { - return this.downloadStartedMock(name); + downloadStarted( + name: string, + extension: string, + size: number + ): Promise { + return this.downloadStartedMock(name, extension, size); } - downloadUpdate(name: string, progress: number): Promise { - return this.downloadUpdateMock(name, progress); + + downloadUpdate( + name: string, + extension: string, + size: number, + progress: { elapsedTime: number; percentage: number } + ): Promise { + return this.downloadUpdateMock(name, extension, size, progress); } - downloadFinished(name: string): Promise { - return this.downloadFinishedMock(name); + + downloadFinished( + name: string, + extension: string, + size: number, + progress: { elapsedTime: number } + ): Promise { + return this.downloadFinishedMock(name, extension, size, progress); } - error(name: string): Promise { - return this.errorMock(name); + + error(name: string, extension: string): Promise { + return this.errorMock(name, extension); } } From 3a22d9e8720162c43fd238e368171df3eec2f91e Mon Sep 17 00:00:00 2001 From: joan vicens Date: Mon, 26 Feb 2024 12:34:52 +0100 Subject: [PATCH 06/15] chore: upadate dependcies --- yarn.lock | 241 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 200 insertions(+), 41 deletions(-) diff --git a/yarn.lock b/yarn.lock index 075c2b63d..2b7ecb16d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1201,20 +1201,21 @@ glob "^7.1.6" minimatch "^3.0.4" -"@electron/get@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" - integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== +"@electron/get@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40" + integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw== dependencies: debug "^4.1.1" env-paths "^2.2.0" fs-extra "^8.1.0" - got "^11.8.5" + got "^9.6.0" progress "^2.0.3" semver "^6.2.0" sumchecker "^3.0.1" optionalDependencies: global-agent "^3.0.0" + global-tunnel-ng "^2.7.1" "@electron/universal@1.2.1": version "1.2.1" @@ -2323,6 +2324,13 @@ "@svgr/plugin-jsx" "^6.5.1" "@svgr/plugin-svgo" "^6.5.1" +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" @@ -2740,12 +2748,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.32.tgz#5b5becc5da76fc055b2a601c8a3adbf13891227e" integrity sha512-zpnXe4dEz6PrWz9u7dqyRoq9VxwCvoXRPy/ewhmMa1CgEyVmtL1NJPQ2MX+4pf97vetquVKkpiMx0MwI8pjNOw== -"@types/node@^18.11.18": - version "18.19.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.14.tgz#1880ff1b3ac913f3877f711588e5ed227da01886" - integrity sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg== - dependencies: - undici-types "~5.26.4" +"@types/node@^16.11.26": + version "16.18.83" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.83.tgz#681d1a20676d24fc47e2da3934536304097a81d8" + integrity sha512-TmBqzDY/GeCEmLob/31SunOQnqYE3ZiiuEh1U9o3HqE1E2cqKZQA5RQg4krEguCY3StnkXyDmCny75qyFLx/rA== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2932,13 +2938,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yauzl@^2.9.1": - version "2.10.3" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" - integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.4.0", "@typescript-eslint/eslint-plugin@^5.51.0", "@typescript-eslint/eslint-plugin@^5.6.0", "@typescript-eslint/eslint-plugin@^5.8.1": version "5.59.11" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz#8d466aa21abea4c3f37129997b198d141f09e76f" @@ -4023,6 +4022,19 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + cacheable-request@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" @@ -4420,6 +4432,16 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concat-stream@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + concurrently@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.5.1.tgz#4518c67f7ac680cf5c34d5adf399a2a2047edc8c" @@ -4450,6 +4472,14 @@ conf@^10.2.0: pkg-up "^3.1.0" semver "^7.3.5" +config-chain@^1.1.11: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + confusing-browser-globals@^1.0.10: version "1.0.11" resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" @@ -4764,7 +4794,7 @@ debounce@^1.2.1: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== -debug@2.6.9, debug@^2.6.8: +debug@2.6.9, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -4813,6 +4843,13 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== + dependencies: + mimic-response "^1.0.0" + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -4878,6 +4915,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + defer-to-connect@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" @@ -5177,6 +5219,11 @@ duplexer2@^0.1.2: dependencies: readable-stream "^2.0.2" +duplexer3@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5366,9 +5413,9 @@ electron@^19.1.9: resolved "https://registry.yarnpkg.com/electron/-/electron-19.1.9.tgz#01995eea4014f7cdb2f616f5f3492d4ed6f5e4f0" integrity sha512-XT5LkTzIHB+ZtD3dTmNnKjVBWrDWReCKt9G1uAFLz6uJMEVcIUiYO+fph5pLXETiBw/QZBx8egduMEfIccLx+g== dependencies: - "@electron/get" "^2.0.0" - "@types/node" "^18.11.18" - extract-zip "^2.0.1" + "@electron/get" "^1.14.1" + "@types/node" "^16.11.26" + extract-zip "^1.0.3" emittery@^0.8.1: version "0.8.1" @@ -5395,7 +5442,7 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== -encodeurl@~1.0.2: +encodeurl@^1.0.2, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== @@ -6074,16 +6121,15 @@ express@^4.17.3, express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== +extract-zip@^1.0.3: + version "1.7.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" + integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== dependencies: - debug "^4.1.1" - get-stream "^5.1.0" + concat-stream "^1.6.2" + debug "^2.6.9" + mkdirp "^0.5.4" yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" extsprintf@^1.2.0: version "1.4.1" @@ -6448,6 +6494,13 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -6534,6 +6587,16 @@ global-agent@^3.0.0: semver "^7.3.2" serialize-error "^7.0.1" +global-tunnel-ng@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" + integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== + dependencies: + encodeurl "^1.0.2" + lodash "^4.17.10" + npm-conf "^1.1.3" + tunnel "^0.0.6" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -6603,7 +6666,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -got@^11.7.0, got@^11.8.5: +got@^11.7.0: version "11.8.6" resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== @@ -6620,6 +6683,23 @@ got@^11.7.0, got@^11.8.5: p-cancelable "^2.0.0" responselike "^2.0.0" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -7029,6 +7109,11 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== +ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" @@ -7981,6 +8066,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -8110,6 +8200,13 @@ keyboardevents-areequal@^0.2.1: resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + keyv@^4.0.0: version "4.5.2" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.2.tgz#0e310ce73bf7851ec702f2eaf46ec4e3805cce56" @@ -8349,7 +8446,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8415,6 +8512,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -8633,7 +8735,7 @@ mimic-fn@^3.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== -mimic-response@^1.0.0: +mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== @@ -8766,7 +8868,7 @@ minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" -mkdirp@^0.5.1, mkdirp@^0.5.5: +mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -8972,11 +9074,24 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== +npm-conf@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" + integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -9175,6 +9290,11 @@ ora@^5.1.0: strip-ansi "^6.0.0" wcwidth "^1.0.1" +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + p-cancelable@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" @@ -9363,6 +9483,11 @@ pify@^2.3.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== + pirates@^4.0.1, pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -9709,6 +9834,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -9787,6 +9917,11 @@ prop-types@^15.6.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -10056,7 +10191,7 @@ read-pkg@^6.0.0: parse-json "^5.2.0" type-fest "^1.0.1" -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@~2.3.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -10246,6 +10381,13 @@ resolve@^2.0.0-next.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== + dependencies: + lowercase-keys "^1.0.0" + responselike@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" @@ -11262,6 +11404,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -11403,6 +11550,11 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -11471,6 +11623,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + typescript@^4.5.2: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" @@ -11491,11 +11648,6 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - undici@^5.5.1: version "5.22.1" resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" @@ -11593,6 +11745,13 @@ url-loader@^4.1.1: mime-types "^2.1.27" schema-utils "^3.0.0" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== + dependencies: + prepend-http "^2.0.0" + url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" From bcff7ebfbc9fac6d78569837f75a73adfd0bc45f Mon Sep 17 00:00:00 2001 From: joan vicens Date: Mon, 26 Feb 2024 17:30:14 +0100 Subject: [PATCH 07/15] wip --- release/app/package.json | 1 - .../virtual-drive/files/FilesContainer.ts | 4 +-- .../virtual-drive/files/builder.ts | 10 ++++-- src/apps/hydration-api/controllers/files.ts | 10 +++--- .../files/FilesContainer.ts | 4 +-- .../dependency-injection/files/builder.ts | 4 +-- src/apps/sync-engine/BindingManager.ts | 2 +- .../callbacks-controllers/buildControllers.ts | 2 +- .../controllers/DownloadFileController.ts | 8 ++--- .../files/FilesContainer.ts | 4 +-- .../dependency-injection/files/builder.ts | 13 +++++--- .../files/application/FileCreator.ts | 10 ++++-- .../files/application/FileDeleter.ts | 5 +-- .../application/FileFinderByContentsId.ts | 17 ---------- .../files/application/FilePathUpdater.ts | 24 +++++++------- .../FilePlaceholderCreatorFromContentsId.ts | 8 ++--- .../application/FilesPlaceholderUpdater.ts | 4 +-- .../files/application/FilesSearcher.ts | 12 ------- .../files/application/SameFileWasMoved.ts | 12 ++++--- .../files/domain/FileRepository.ts | 6 +++- .../infrastructure/InMemoryFileRepository.ts | 32 ++++++++++++++++--- .../files/__mocks__/FileRepositoryMock.ts | 17 +++++++++- .../files/application/FilePathUpdater.test.ts | 4 +++ 23 files changed, 120 insertions(+), 93 deletions(-) delete mode 100644 src/context/virtual-drive/files/application/FileFinderByContentsId.ts delete mode 100644 src/context/virtual-drive/files/application/FilesSearcher.ts diff --git a/release/app/package.json b/release/app/package.json index c0cf7e5f5..36761c25b 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -10,7 +10,6 @@ "postinstall": "npm run electron-rebuild --sequential && npm run link-modules" }, "dependencies": { - "@gcas/fuse": "^2.4.2", "@rudderstack/rudder-sdk-node": "^1.1.4", "better-sqlite3": "^8.3.0", "typeorm": "^0.3.16" diff --git a/src/apps/fuse/dependency-injection/virtual-drive/files/FilesContainer.ts b/src/apps/fuse/dependency-injection/virtual-drive/files/FilesContainer.ts index 78d6c8072..95126b76a 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/files/FilesContainer.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/files/FilesContainer.ts @@ -1,4 +1,4 @@ -import { FilesSearcher } from '../../../../../context/virtual-drive/files/application/FilesSearcher'; +import { FirstsFileSearcher } from '../../../../../context/virtual-drive/files/application/FirstsFileSearcher'; import { FilesByFolderPathSearcher } from '../../../../../context/virtual-drive/files/application/FilesByFolderPathSearcher'; import { FilePathUpdater } from '../../../../../context/virtual-drive/files/application/FilePathUpdater'; import { FileCreator } from '../../../../../context/virtual-drive/files/application/FileCreator'; @@ -10,7 +10,7 @@ import { SyncFileMessenger } from '../../../../../context/virtual-drive/files/do export interface FilesContainer { filesByFolderPathNameLister: FilesByFolderPathSearcher; - filesSearcher: FilesSearcher; + filesSearcher: FirstsFileSearcher; filePathUpdater: FilePathUpdater; fileCreator: FileCreator; fileDeleter: FileDeleter; diff --git a/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts b/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts index bb4e9f9ad..2042d7679 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts @@ -4,7 +4,7 @@ import { FileCreator } from '../../../../../context/virtual-drive/files/applicat import { FileDeleter } from '../../../../../context/virtual-drive/files/application/FileDeleter'; import { FilePathUpdater } from '../../../../../context/virtual-drive/files/application/FilePathUpdater'; import { FilesByFolderPathSearcher } from '../../../../../context/virtual-drive/files/application/FilesByFolderPathSearcher'; -import { FilesSearcher } from '../../../../../context/virtual-drive/files/application/FilesSearcher'; +import { FirstsFileSearcher } from '../../../../../context/virtual-drive/files/application/FirstsFileSearcher'; import { FileRepositoryInitializer } from '../../../../../context/virtual-drive/files/application/FileRepositoryInitializer'; import { SameFileWasMoved } from '../../../../../context/virtual-drive/files/application/SameFileWasMoved'; import { File } from '../../../../../context/virtual-drive/files/domain/File'; @@ -20,6 +20,7 @@ import { FoldersContainer } from '../folders/FoldersContainer'; import { SharedContainer } from '../shared/SharedContainer'; import { FilesContainer } from './FilesContainer'; import { InMemoryFileRepositorySingleton } from '../../../../shared/dependency-injection/virtual-drive/files/InMemoryFileRepositorySingleton'; +import { SingleFileMatchingSearcher } from '../../../../../context/virtual-drive/files/application/SingleFileMatchingSearcher'; export async function buildFilesContainer( initialFiles: Array, @@ -44,7 +45,7 @@ export async function buildFilesContainer( folderContainer.folderFinder ); - const filesSearcher = new FilesSearcher(repository); + const filesSearcher = new FirstsFileSearcher(repository); const remoteFileSystem = new SDKRemoteFileSystem( sdk, @@ -56,16 +57,19 @@ export async function buildFilesContainer( sharedContainer.relativePathToAbsoluteConverter ); + const singleFileMatchingSearcher = new SingleFileMatchingSearcher(repository); + const filePathUpdater = new FilePathUpdater( remoteFileSystem, localFileSystem, repository, + singleFileMatchingSearcher, folderContainer.folderFinder, eventBus ); const sameFileWasMoved = new SameFileWasMoved( - repository, + singleFileMatchingSearcher, localFileSystem, eventRepository ); diff --git a/src/apps/hydration-api/controllers/files.ts b/src/apps/hydration-api/controllers/files.ts index 163ec095d..026ec6b4f 100644 --- a/src/apps/hydration-api/controllers/files.ts +++ b/src/apps/hydration-api/controllers/files.ts @@ -6,10 +6,12 @@ export function buildFilesControllers(container: DependencyContainer) { const getAll = async (_req: Request, res: Response) => { const files = await container.retrieveAllFiles.run(); - res.status(200).send({ files }); + const result = files.map((file) => file.attributes()); + + res.status(200).send({ files: result }); }; - const getByPartial = async (req: Request, res: Response) => { + const filter = async (req: Request, res: Response) => { const filter = Object.entries(req.query) .map(([key, param]) => { @@ -29,8 +31,8 @@ export function buildFilesControllers(container: DependencyContainer) { return; } - res.status(200).send({ file }); + res.status(200).send({ file: file.attributes() }); }; - return { getAll, getByPartial }; + return { getAll, getByPartial: filter }; } diff --git a/src/apps/hydration-api/dependency-injection/files/FilesContainer.ts b/src/apps/hydration-api/dependency-injection/files/FilesContainer.ts index 062fed065..69536da8d 100644 --- a/src/apps/hydration-api/dependency-injection/files/FilesContainer.ts +++ b/src/apps/hydration-api/dependency-injection/files/FilesContainer.ts @@ -1,9 +1,9 @@ -import { FilesSearcher } from '../../../../context/virtual-drive/files/application/FilesSearcher'; +import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/FirstsFileSearcher'; import { FilesByFolderPathSearcher } from '../../../../context/virtual-drive/files/application/FilesByFolderPathSearcher'; import { RetrieveAllFiles } from '../../../../context/virtual-drive/files/application/RetrieveAllFiles'; export interface FilesContainer { filesByFolderPathNameLister: FilesByFolderPathSearcher; - filesSearcher: FilesSearcher; + filesSearcher: FirstsFileSearcher; retrieveAllFiles: RetrieveAllFiles; } diff --git a/src/apps/hydration-api/dependency-injection/files/builder.ts b/src/apps/hydration-api/dependency-injection/files/builder.ts index 5277af7e0..8d8a00caa 100644 --- a/src/apps/hydration-api/dependency-injection/files/builder.ts +++ b/src/apps/hydration-api/dependency-injection/files/builder.ts @@ -1,5 +1,5 @@ import { FilesByFolderPathSearcher } from '../../../../context/virtual-drive/files/application/FilesByFolderPathSearcher'; -import { FilesSearcher } from '../../../../context/virtual-drive/files/application/FilesSearcher'; +import { FirstsFileSearcher } from '../../../../context/virtual-drive/files/application/FirstsFileSearcher'; import { RetrieveAllFiles } from '../../../../context/virtual-drive/files/application/RetrieveAllFiles'; import { InMemoryFileRepositorySingleton } from '../../../shared/dependency-injection/virtual-drive/files/InMemoryFileRepositorySingleton'; import { FoldersContainer } from '../folders/FoldersContainer'; @@ -15,7 +15,7 @@ export async function buildFilesContainer( folderContainer.folderFinder ); - const filesSearcher = new FilesSearcher(repository); + const filesSearcher = new FirstsFileSearcher(repository); const retrieveAllFiles = new RetrieveAllFiles(repository); diff --git a/src/apps/sync-engine/BindingManager.ts b/src/apps/sync-engine/BindingManager.ts index 8a00b08ce..8a53f5e7c 100644 --- a/src/apps/sync-engine/BindingManager.ts +++ b/src/apps/sync-engine/BindingManager.ts @@ -91,7 +91,7 @@ export class BindingsManager { callback ); Logger.debug('Execute Fetch Data Callback, sending path:', path); - const file = controllers.downloadFile.fileFinderByContentsId( + const file = await controllers.downloadFile.fileFinderByContentsId( contentsId .replace( // eslint-disable-next-line no-control-regex diff --git a/src/apps/sync-engine/callbacks-controllers/buildControllers.ts b/src/apps/sync-engine/callbacks-controllers/buildControllers.ts index c492d32c5..0612327b1 100644 --- a/src/apps/sync-engine/callbacks-controllers/buildControllers.ts +++ b/src/apps/sync-engine/callbacks-controllers/buildControllers.ts @@ -27,7 +27,7 @@ export function buildControllers(container: SyncEngineDependencyContainer) { ); const downloadFileController = new DownloadFileController( - container.fileFinderByContentsId, + container.singleFileMatchingFinder, container.contentsDownloader ); diff --git a/src/apps/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts b/src/apps/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts index 6e5b8bfbc..67f9f12c8 100644 --- a/src/apps/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts +++ b/src/apps/sync-engine/callbacks-controllers/controllers/DownloadFileController.ts @@ -1,26 +1,26 @@ import Logger from 'electron-log'; import { ContentsDownloader } from '../../../../context/virtual-drive/contents/application/ContentsDownloader'; -import { FileFinderByContentsId } from '../../../../context/virtual-drive/files/application/FileFinderByContentsId'; import { FilePlaceholderId } from '../../../../context/virtual-drive/files/domain/PlaceholderId'; import { CallbackDownload } from '../../BindingManager'; import { CallbackController } from './CallbackController'; +import { SingleFileMatchingFinder } from '../../../../context/virtual-drive/files/application/SingleFileMatchingFinder'; export class DownloadFileController extends CallbackController { constructor( - private readonly fileFinder: FileFinderByContentsId, + private readonly fileFinder: SingleFileMatchingFinder, private readonly downloader: ContentsDownloader ) { super(); } private async action(id: string, cb: CallbackDownload): Promise { - const file = this.fileFinder.run(id); + const file = await this.fileFinder.run({ contentsId: id }); return await this.downloader.run(file, cb); } fileFinderByContentsId(contentsId: string) { - return this.fileFinder.run(contentsId); + return this.fileFinder.run({ contentsId }); } async execute( diff --git a/src/apps/sync-engine/dependency-injection/files/FilesContainer.ts b/src/apps/sync-engine/dependency-injection/files/FilesContainer.ts index bf9fcf40a..48a99c5c7 100644 --- a/src/apps/sync-engine/dependency-injection/files/FilesContainer.ts +++ b/src/apps/sync-engine/dependency-injection/files/FilesContainer.ts @@ -1,7 +1,6 @@ import { CreateFilePlaceholderOnDeletionFailed } from '../../../../context/virtual-drive/files/application/CreateFilePlaceholderOnDeletionFailed'; import { FileCreator } from '../../../../context/virtual-drive/files/application/FileCreator'; import { FileDeleter } from '../../../../context/virtual-drive/files/application/FileDeleter'; -import { FileFinderByContentsId } from '../../../../context/virtual-drive/files/application/FileFinderByContentsId'; import { FilePathUpdater } from '../../../../context/virtual-drive/files/application/FilePathUpdater'; import { FilePlaceholderCreatorFromContentsId } from '../../../../context/virtual-drive/files/application/FilePlaceholderCreatorFromContentsId'; import { FilesPlaceholderUpdater } from '../../../../context/virtual-drive/files/application/FilesPlaceholderUpdater'; @@ -9,9 +8,9 @@ import { FilesPlaceholderCreator } from '../../../../context/virtual-drive/files import { FileRepositoryInitializer } from '../../../../context/virtual-drive/files/application/FileRepositoryInitializer'; import { RetrieveAllFiles } from '../../../../context/virtual-drive/files/application/RetrieveAllFiles'; import { SameFileWasMoved } from '../../../../context/virtual-drive/files/application/SameFileWasMoved'; +import { SingleFileMatchingFinder } from '../../../../context/virtual-drive/files/application/SingleFileMatchingFinder'; export interface FilesContainer { - fileFinderByContentsId: FileFinderByContentsId; fileDeleter: FileDeleter; filePathUpdater: FilePathUpdater; fileCreator: FileCreator; @@ -22,4 +21,5 @@ export interface FilesContainer { repositoryPopulator: FileRepositoryInitializer; filesPlaceholderCreator: FilesPlaceholderCreator; filesPlaceholderUpdater: FilesPlaceholderUpdater; + singleFileMatchingFinder: SingleFileMatchingFinder; } diff --git a/src/apps/sync-engine/dependency-injection/files/builder.ts b/src/apps/sync-engine/dependency-injection/files/builder.ts index ca9acc619..1f7f1add1 100644 --- a/src/apps/sync-engine/dependency-injection/files/builder.ts +++ b/src/apps/sync-engine/dependency-injection/files/builder.ts @@ -2,7 +2,6 @@ import crypt from '../../../../context/shared/infrastructure/crypt'; import { CreateFilePlaceholderOnDeletionFailed } from '../../../../context/virtual-drive/files/application/CreateFilePlaceholderOnDeletionFailed'; import { FileCreator } from '../../../../context/virtual-drive/files/application/FileCreator'; import { FileDeleter } from '../../../../context/virtual-drive/files/application/FileDeleter'; -import { FileFinderByContentsId } from '../../../../context/virtual-drive/files/application/FileFinderByContentsId'; import { FilePathUpdater } from '../../../../context/virtual-drive/files/application/FilePathUpdater'; import { FilePlaceholderCreatorFromContentsId } from '../../../../context/virtual-drive/files/application/FilePlaceholderCreatorFromContentsId'; import { FilesPlaceholderUpdater } from '../../../../context/virtual-drive/files/application/FilesPlaceholderUpdater'; @@ -25,6 +24,8 @@ import { DependencyInjectionVirtualDrive } from '../common/virtualDrive'; import { FoldersContainer } from '../folders/FoldersContainer'; import { SharedContainer } from '../shared/SharedContainer'; import { FilesContainer } from './FilesContainer'; +import { SingleFileMatchingSearcher } from '../../../../context/virtual-drive/files/application/SingleFileMatchingSearcher'; +import { SingleFileMatchingFinder } from '../../../../context/virtual-drive/files/application/SingleFileMatchingFinder'; export async function buildFilesContainer( folderContainer: FoldersContainer, @@ -58,7 +59,8 @@ export async function buildFilesContainer( const repository = new InMemoryFileRepository(); - const fileFinderByContentsId = new FileFinderByContentsId(repository); + const singleFileMatchingSearcher = new SingleFileMatchingSearcher(repository); + const singleFileMatchingFinder = new SingleFileMatchingFinder(repository); const fileDeleter = new FileDeleter( remoteFileSystem, @@ -69,7 +71,7 @@ export async function buildFilesContainer( ); const sameFileWasMoved = new SameFileWasMoved( - repository, + singleFileMatchingSearcher, localFileSystem, eventHistory ); @@ -78,6 +80,7 @@ export async function buildFilesContainer( remoteFileSystem, localFileSystem, repository, + singleFileMatchingSearcher, folderContainer.folderFinder, eventBus ); @@ -93,7 +96,7 @@ export async function buildFilesContainer( const filePlaceholderCreatorFromContentsId = new FilePlaceholderCreatorFromContentsId( - fileFinderByContentsId, + singleFileMatchingFinder, localFileSystem ); @@ -119,7 +122,6 @@ export async function buildFilesContainer( ); const container: FilesContainer = { - fileFinderByContentsId, fileDeleter, filePathUpdater, fileCreator, @@ -131,6 +133,7 @@ export async function buildFilesContainer( repositoryPopulator: repositoryPopulator, filesPlaceholderCreator, filesPlaceholderUpdater, + singleFileMatchingFinder, }; return { container, subscribers: [] }; diff --git a/src/context/virtual-drive/files/application/FileCreator.ts b/src/context/virtual-drive/files/application/FileCreator.ts index 64ceac690..31b647e2c 100644 --- a/src/context/virtual-drive/files/application/FileCreator.ts +++ b/src/context/virtual-drive/files/application/FileCreator.ts @@ -29,13 +29,17 @@ export class FileCreator { size: number ): Promise { try { - const existingFile = this.repository.searchByPartial({ + const existingFiles = this.repository.matchingPartial({ path: PlatformPathConverter.winToPosix(filePath.value), status: FileStatuses.EXISTS, }); - if (existingFile) { - await this.fileDeleter.run(existingFile.contentsId); + if (existingFiles) { + await Promise.all( + existingFiles.map((existingFile) => + this.fileDeleter.run(existingFile.contentsId) + ) + ); } const fileSize = new FileSize(size); diff --git a/src/context/virtual-drive/files/application/FileDeleter.ts b/src/context/virtual-drive/files/application/FileDeleter.ts index 7044ae795..83a3d541b 100644 --- a/src/context/virtual-drive/files/application/FileDeleter.ts +++ b/src/context/virtual-drive/files/application/FileDeleter.ts @@ -18,10 +18,7 @@ export class FileDeleter { ) {} async run(contentsId: File['contentsId']): Promise { - const file = this.repository.searchByPartial({ - contentsId, - status: FileStatuses.EXISTS, - }); + const file = await this.repository.searchByContentsId(contentsId); if (!file) { return; diff --git a/src/context/virtual-drive/files/application/FileFinderByContentsId.ts b/src/context/virtual-drive/files/application/FileFinderByContentsId.ts deleted file mode 100644 index e4eb8271d..000000000 --- a/src/context/virtual-drive/files/application/FileFinderByContentsId.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { FileNotFoundError } from '../domain/errors/FileNotFoundError'; -import { File } from '../domain/File'; -import { FileRepository } from '../domain/FileRepository'; - -export class FileFinderByContentsId { - constructor(private readonly repository: FileRepository) {} - - run(contentsId: string): File { - const file = this.repository.searchByPartial({ contentsId }); - - if (!file) { - throw new FileNotFoundError(contentsId); - } - - return file; - } -} diff --git a/src/context/virtual-drive/files/application/FilePathUpdater.ts b/src/context/virtual-drive/files/application/FilePathUpdater.ts index 683afa0b2..bb588ce78 100644 --- a/src/context/virtual-drive/files/application/FilePathUpdater.ts +++ b/src/context/virtual-drive/files/application/FilePathUpdater.ts @@ -1,23 +1,25 @@ -import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; -import { FileAlreadyExistsError } from '../domain/errors/FileAlreadyExistsError'; -import { FilePath } from '../domain/FilePath'; -import { File } from '../domain/File'; +import Logger from 'electron-log'; import { FolderFinder } from '../../folders/application/FolderFinder'; import { EventBus } from '../../shared/domain/EventBus'; +import { File } from '../domain/File'; +import { FilePath } from '../domain/FilePath'; import { FileRepository } from '../domain/FileRepository'; -import { RemoteFileSystem } from '../domain/file-systems/RemoteFileSystem'; -import { LocalFileSystem } from '../domain/file-systems/LocalFileSystem'; -import { FileRenameFailedDomainEvent } from '../domain/events/FileRenameFailedDomainEvent'; -import { FileRenameStartedDomainEvent } from '../domain/events/FileRenameStartedDomainEvent'; import { FileStatuses } from '../domain/FileStatus'; +import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; +import { FileAlreadyExistsError } from '../domain/errors/FileAlreadyExistsError'; import { FileNotFoundError } from '../domain/errors/FileNotFoundError'; -import Logger from 'electron-log'; +import { FileRenameFailedDomainEvent } from '../domain/events/FileRenameFailedDomainEvent'; +import { FileRenameStartedDomainEvent } from '../domain/events/FileRenameStartedDomainEvent'; +import { LocalFileSystem } from '../domain/file-systems/LocalFileSystem'; +import { RemoteFileSystem } from '../domain/file-systems/RemoteFileSystem'; +import { SingleFileMatchingSearcher } from './SingleFileMatchingSearcher'; export class FilePathUpdater { constructor( private readonly remote: RemoteFileSystem, private readonly local: LocalFileSystem, private readonly repository: FileRepository, + private readonly singleFileMatching: SingleFileMatchingSearcher, private readonly folderFinder: FolderFinder, private readonly eventBus: EventBus ) {} @@ -56,7 +58,7 @@ export class FilePathUpdater { async run(contentsId: string, posixRelativePath: string) { const destination = new FilePath(posixRelativePath); - const file = this.repository.searchByPartial({ + const file = await this.singleFileMatching.run({ contentsId, status: FileStatuses.EXISTS, }); @@ -76,7 +78,7 @@ export class FilePathUpdater { return; } - const destinationFile = this.repository.searchByPartial({ + const destinationFile = this.repository.matchingPartial({ path: destination.value, status: FileStatuses.EXISTS, }); diff --git a/src/context/virtual-drive/files/application/FilePlaceholderCreatorFromContentsId.ts b/src/context/virtual-drive/files/application/FilePlaceholderCreatorFromContentsId.ts index 691aa35d0..3eada1c4a 100644 --- a/src/context/virtual-drive/files/application/FilePlaceholderCreatorFromContentsId.ts +++ b/src/context/virtual-drive/files/application/FilePlaceholderCreatorFromContentsId.ts @@ -1,15 +1,15 @@ import { File } from '../domain/File'; import { LocalFileSystem } from '../domain/file-systems/LocalFileSystem'; -import { FileFinderByContentsId } from './FileFinderByContentsId'; +import { SingleFileMatchingFinder } from './SingleFileMatchingFinder'; export class FilePlaceholderCreatorFromContentsId { constructor( - private readonly finder: FileFinderByContentsId, + private readonly finder: SingleFileMatchingFinder, private readonly local: LocalFileSystem ) {} - run(contentsId: File['contentsId']) { - const file = this.finder.run(contentsId); + async run(contentsId: File['contentsId']) { + const file = await this.finder.run({ contentsId }); this.local.createPlaceHolder(file); } diff --git a/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts b/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts index a59899534..1f0d782cc 100644 --- a/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts +++ b/src/context/virtual-drive/files/application/FilesPlaceholderUpdater.ts @@ -27,9 +27,7 @@ export class FilesPlaceholderUpdater { } private async update(remote: File): Promise { - const local = this.repository.searchByPartial({ - contentsId: remote.contentsId, - }); + const local = await this.repository.searchByContentsId(remote.contentsId); if (!local) { if (remote.status.is(FileStatuses.EXISTS)) { diff --git a/src/context/virtual-drive/files/application/FilesSearcher.ts b/src/context/virtual-drive/files/application/FilesSearcher.ts deleted file mode 100644 index 42f9f5f64..000000000 --- a/src/context/virtual-drive/files/application/FilesSearcher.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { File, FileAttributes } from '../domain/File'; -import { FileRepository } from '../domain/FileRepository'; - -export class FilesSearcher { - constructor(private readonly repository: FileRepository) {} - - async run(attributes: Partial): Promise { - const file = this.repository.searchByPartial(attributes); - - return file; - } -} diff --git a/src/context/virtual-drive/files/application/SameFileWasMoved.ts b/src/context/virtual-drive/files/application/SameFileWasMoved.ts index 334e8d6e4..19b5f6a5a 100644 --- a/src/context/virtual-drive/files/application/SameFileWasMoved.ts +++ b/src/context/virtual-drive/files/application/SameFileWasMoved.ts @@ -1,23 +1,25 @@ +import Logger from 'electron-log'; import { EventRepository } from '../../shared/domain/EventRepository'; import { FilePath } from '../domain/FilePath'; -import { FileRepository } from '../domain/FileRepository'; +import { FileStatuses } from '../domain/FileStatus'; import { FileMovedDomainEvent } from '../domain/events/FileMovedDomainEvent'; -import Logger from 'electron-log'; import { LocalFileSystem } from '../domain/file-systems/LocalFileSystem'; +import { SingleFileMatchingSearcher } from './SingleFileMatchingSearcher'; // TODO: find a better name type WasMovedResult = { result: false } | { result: true; contentsId: string }; export class SameFileWasMoved { constructor( - private readonly repository: FileRepository, + private readonly singleFileSearcher: SingleFileMatchingSearcher, private readonly localFileSystem: LocalFileSystem, private readonly eventHistory: EventRepository ) {} async run(path: FilePath): Promise { - const fileInDestination = this.repository.searchByPartial({ + const fileInDestination = await this.singleFileSearcher.run({ path: path.value, + status: FileStatuses.EXISTS, }); if (!fileInDestination) { @@ -38,7 +40,7 @@ export class SameFileWasMoved { return { result: false }; } - const file = this.repository.searchByPartial({ + const file = await this.singleFileSearcher.run({ contentsId: movedEvent.aggregateId, }); diff --git a/src/context/virtual-drive/files/domain/FileRepository.ts b/src/context/virtual-drive/files/domain/FileRepository.ts index e08d455c1..e79b1dfa7 100644 --- a/src/context/virtual-drive/files/domain/FileRepository.ts +++ b/src/context/virtual-drive/files/domain/FileRepository.ts @@ -3,7 +3,11 @@ import { File, FileAttributes } from './File'; export interface FileRepository { all(): Promise>; - searchByPartial(partial: Partial): File | undefined; + matchingPartial(partial: Partial): Array | undefined; + + searchById(id: File['id']): Promise; + + searchByContentsId(id: File['contentsId']): Promise; listByPartial(partial: Partial): Promise>; diff --git a/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts b/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts index 211bf871e..6e9abeab3 100644 --- a/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts +++ b/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts @@ -2,7 +2,7 @@ import { File, FileAttributes } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; export class InMemoryFileRepository implements FileRepository { - private files: Map; + private files: Map; private get values(): Array { return Array.from(this.files.values()); @@ -17,6 +17,28 @@ export class InMemoryFileRepository implements FileRepository { } } + async searchById(id: number): Promise { + const files = this.files.values(); + + for (const attributes of files) { + if (id === attributes.id) { + return File.from(attributes); + } + } + + return undefined; + } + + async searchByContentsId(id: string): Promise { + const attributes = this.files.get(id); + + if (!attributes) { + return; + } + + return File.from(attributes); + } + public all(): Promise> { const files = [...this.files.values()].map((attributes) => File.from(attributes) @@ -24,10 +46,10 @@ export class InMemoryFileRepository implements FileRepository { return Promise.resolve(files); } - searchByPartial(partial: Partial): File | undefined { + matchingPartial(partial: Partial): Array | undefined { const keys = Object.keys(partial) as Array>; - const file = this.values.find((attributes) => { + const filesAttributes = this.values.filter((attributes) => { return keys.every((key: keyof FileAttributes) => { if (key === 'contentsId') { return ( @@ -39,8 +61,8 @@ export class InMemoryFileRepository implements FileRepository { }); }); - if (file) { - return File.from(file); + if (filesAttributes) { + return filesAttributes.map((attributes) => File.from(attributes)); } return undefined; diff --git a/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts b/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts index 519b31a89..855a51cac 100644 --- a/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts +++ b/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts @@ -8,6 +8,8 @@ import { FileRepository } from '../../../../../src/context/virtual-drive/files/d export class FileRepositoryMock implements FileRepository { public readonly allMock = jest.fn(); public readonly searchByPartialMock = jest.fn(); + public readonly searchByIdMock = jest.fn(); + public readonly searchByContentsIdMock = jest.fn(); public readonly listByPartialMock = jest.fn(); public readonly deleteMock = jest.fn(); public readonly addMock = jest.fn(); @@ -16,18 +18,31 @@ export class FileRepositoryMock implements FileRepository { all(): Promise { return this.allMock(); } - searchByPartial(partial: Partial): Nullable { + + matchingPartial(partial: Partial): Nullable> { return this.searchByPartialMock(partial); } + listByPartial(partial: Partial): Promise { return this.listByPartialMock(partial); } + + searchById(id: number): Promise { + return this.searchByIdMock(id); + } + + searchByContentsId(id: string): Promise { + return this.searchByContentsIdMock(id); + } + delete(id: string): Promise { return this.deleteMock(id); } + add(file: File): Promise { return this.addMock(file); } + update(file: File): Promise { return this.updateMock(file); } diff --git a/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts b/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts index 68de50bd7..d5dd80aad 100644 --- a/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts +++ b/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts @@ -7,11 +7,13 @@ import { EventBusMock } from '../../shared/__mock__/EventBusMock'; import { FileRepositoryMock } from '../__mocks__/FileRepositoryMock'; import { LocalFileSystemMock } from '../__mocks__/LocalFileSystemMock'; import { RemoteFileSystemMock } from '../__mocks__/RemoteFileSystemMock'; +import { SingleFileMatchingTestClass } from '../__test-class__/SingleFileMatchingTestClass'; import { FileMother } from '../domain/FileMother'; describe('File path updater', () => { let repository: FileRepositoryMock; let folderFinder: FolderFinderMock; + let singleFileMatchingTestClass: SingleFileMatchingTestClass; let localFileSystem: LocalFileSystemMock; let eventBus: EventBusMock; let remoteFileSystemMock: RemoteFileSystemMock; @@ -20,6 +22,7 @@ describe('File path updater', () => { beforeEach(() => { repository = new FileRepositoryMock(); folderFinder = new FolderFinderMock(); + singleFileMatchingTestClass = new SingleFileMatchingTestClass(); eventBus = new EventBusMock(); remoteFileSystemMock = new RemoteFileSystemMock(); localFileSystem = new LocalFileSystemMock(); @@ -28,6 +31,7 @@ describe('File path updater', () => { remoteFileSystemMock, localFileSystem, repository, + singleFileMatchingTestClass, folderFinder as unknown as FolderFinder, eventBus ); From b0858009d8eb6dd33ffbb09a55cd9490c80ed110 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 20 Feb 2024 12:22:30 +0100 Subject: [PATCH 08/15] test: event es emmited before the promise fails --- .../EnvironmentContentFileDownloader.test.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts b/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts index 250ab819c..f207a82bc 100644 --- a/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts +++ b/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts @@ -56,7 +56,13 @@ describe('Environment Content File Downloader', () => { await downloader.download(file); - expect(handler.mock.calls).toEqual([[25], [50], [75]]); + const firstArgumentsOfProgress = handler.mock.calls.map( + (args) => args[0] + ); + + expect(firstArgumentsOfProgress).toEqual( + expect.arrayContaining([25, 50, 75]) + ); }); it('emits an event when there is an error', async () => { @@ -78,5 +84,24 @@ describe('Environment Content File Downloader', () => { // no-op }); }); + + it('emits an event before the promises fails', async () => { + const errorMsg = 'Error uploading file'; + const strategy = createDownloadStrategy((callbacks) => { + callbacks.finishedCallback( + { message: errorMsg } as unknown as Error, + Readable.from('') + ); + }); + + const downloader = new EnvironmentContentFileDownloader(strategy, bucket); + + const errorCallback = jest.fn(); + downloader.on('error', errorCallback); + + await downloader.download(file).catch(() => { + expect(errorCallback).toBeCalled(); + }); + }); }); }); From deeb62bb8cf8acc147473483862f8a515d8a6ea5 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 20 Feb 2024 12:32:21 +0100 Subject: [PATCH 09/15] chore: rename test --- .../download/EnvironmentContentFileDownloader.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts b/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts index f207a82bc..9c766c0f4 100644 --- a/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts +++ b/tests/context/virtual-drive/contents/infrastructure/download/EnvironmentContentFileDownloader.test.ts @@ -85,7 +85,7 @@ describe('Environment Content File Downloader', () => { }); }); - it('emits an event before the promises fails', async () => { + it('emits the error event before the promises fails', async () => { const errorMsg = 'Error uploading file'; const strategy = createDownloadStrategy((callbacks) => { callbacks.finishedCallback( From 05fdb50a9dba05a07dfb2a063246e42adaac6205 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Mon, 26 Feb 2024 17:38:32 +0100 Subject: [PATCH 10/15] wip --- .../files/application/FirstsFileSearcher.ts | 14 +++++++++++++ .../application/SingleFileMatchingFinder.ts | 21 +++++++++++++++++++ .../application/SingleFileMatchingSearcher.ts | 18 ++++++++++++++++ .../files/__mocks__/FileRepositoryMock.ts | 4 ++-- .../SingleFileMatchingTestClass.ts | 18 ++++++++++++++++ .../files/application/FileCreator.test.ts | 6 +++--- .../files/application/FileDeleter.test.ts | 8 +++---- .../files/application/FilePathUpdater.test.ts | 6 +++--- 8 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 src/context/virtual-drive/files/application/FirstsFileSearcher.ts create mode 100644 src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts create mode 100644 src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts create mode 100644 tests/context/virtual-drive/files/__test-class__/SingleFileMatchingTestClass.ts diff --git a/src/context/virtual-drive/files/application/FirstsFileSearcher.ts b/src/context/virtual-drive/files/application/FirstsFileSearcher.ts new file mode 100644 index 000000000..94b6e14ca --- /dev/null +++ b/src/context/virtual-drive/files/application/FirstsFileSearcher.ts @@ -0,0 +1,14 @@ +import { File, FileAttributes } from '../domain/File'; +import { FileRepository } from '../domain/FileRepository'; + +export class FirstsFileSearcher { + constructor(private readonly repository: FileRepository) {} + + async run(attributes: Partial): Promise { + const files = this.repository.matchingPartial(attributes); + + if (!files) return; + + return files[0]; + } +} diff --git a/src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts b/src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts new file mode 100644 index 000000000..15e5fbb3c --- /dev/null +++ b/src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts @@ -0,0 +1,21 @@ +import { File, FileAttributes } from '../domain/File'; +import { FileRepository } from '../domain/FileRepository'; +import { FileNotFoundError } from '../domain/errors/FileNotFoundError'; + +export class SingleFileMatchingFinder { + constructor(private readonly repository: FileRepository) {} + + async run(partial: Partial): Promise { + const files = this.repository.matchingPartial(partial); + + if (!files) { + throw new FileNotFoundError('unknown'); + } + + if (files.length > 1) { + throw new Error('Expected to find a singular file'); + } + + return files[0]; + } +} diff --git a/src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts b/src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts new file mode 100644 index 000000000..357b7ec3e --- /dev/null +++ b/src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts @@ -0,0 +1,18 @@ +import { File, FileAttributes } from '../domain/File'; +import { FileRepository } from '../domain/FileRepository'; + +export class SingleFileMatchingSearcher { + constructor(private readonly repository: FileRepository) {} + + async run(attributes: Partial): Promise { + const files = this.repository.matchingPartial(attributes); + + if (!files) return; + + if (files.length > 1) { + throw new Error('Expected to find a singular file'); + } + + return files[0]; + } +} diff --git a/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts b/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts index 855a51cac..d14b55686 100644 --- a/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts +++ b/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts @@ -7,7 +7,7 @@ import { FileRepository } from '../../../../../src/context/virtual-drive/files/d export class FileRepositoryMock implements FileRepository { public readonly allMock = jest.fn(); - public readonly searchByPartialMock = jest.fn(); + public readonly matchingPartialMock = jest.fn(); public readonly searchByIdMock = jest.fn(); public readonly searchByContentsIdMock = jest.fn(); public readonly listByPartialMock = jest.fn(); @@ -20,7 +20,7 @@ export class FileRepositoryMock implements FileRepository { } matchingPartial(partial: Partial): Nullable> { - return this.searchByPartialMock(partial); + return this.matchingPartialMock(partial); } listByPartial(partial: Partial): Promise { diff --git a/tests/context/virtual-drive/files/__test-class__/SingleFileMatchingTestClass.ts b/tests/context/virtual-drive/files/__test-class__/SingleFileMatchingTestClass.ts new file mode 100644 index 000000000..651be9e72 --- /dev/null +++ b/tests/context/virtual-drive/files/__test-class__/SingleFileMatchingTestClass.ts @@ -0,0 +1,18 @@ +import { SingleFileMatchingSearcher } from '../../../../../src/context/virtual-drive/files/application/SingleFileMatchingSearcher'; +import { + FileAttributes, + File, +} from '../../../../../src/context/virtual-drive/files/domain/File'; +import { FileRepository } from '../../../../../src/context/virtual-drive/files/domain/FileRepository'; + +export class SingleFileMatchingTestClass extends SingleFileMatchingSearcher { + public readonly mock = jest.fn(); + + constructor() { + super(undefined as unknown as FileRepository); + } + + run(attributes: Partial): Promise { + return this.mock(attributes); + } +} diff --git a/tests/context/virtual-drive/files/application/FileCreator.test.ts b/tests/context/virtual-drive/files/application/FileCreator.test.ts index 720122295..d226d7dbf 100644 --- a/tests/context/virtual-drive/files/application/FileCreator.test.ts +++ b/tests/context/virtual-drive/files/application/FileCreator.test.ts @@ -95,9 +95,9 @@ describe('File Creator', () => { contentsId: contents.id, }).attributes(); - fileRepository.searchByPartialMock - .mockReturnValueOnce(existingFile) - .mockReturnValueOnce(existingFile); + fileRepository.matchingPartialMock + .mockReturnValueOnce([existingFile]) + .mockReturnValueOnce([existingFile]); const deleterSpy = jest .spyOn(fileDeleter, 'run') diff --git a/tests/context/virtual-drive/files/application/FileDeleter.test.ts b/tests/context/virtual-drive/files/application/FileDeleter.test.ts index d66a8f52f..c49887a93 100644 --- a/tests/context/virtual-drive/files/application/FileDeleter.test.ts +++ b/tests/context/virtual-drive/files/application/FileDeleter.test.ts @@ -40,7 +40,7 @@ describe('File Deleter', () => { it('does not nothing if the file its not found', async () => { const contentsId = ContentsIdMother.raw(); - repository.searchByPartialMock.mockReturnValueOnce(undefined); + repository.searchByContentsIdMock.mockReturnValueOnce(undefined); jest .spyOn(allParentFoldersStatusIsExists, 'run') .mockReturnValueOnce(false); @@ -53,7 +53,7 @@ describe('File Deleter', () => { it('does not delete a file if it has a parent already trashed', async () => { const file = FileMother.any(); - repository.searchByPartialMock.mockReturnValueOnce(file); + repository.searchByContentsIdMock.mockReturnValueOnce(file); jest .spyOn(allParentFoldersStatusIsExists, 'run') .mockReturnValueOnce(false); @@ -66,7 +66,7 @@ describe('File Deleter', () => { it('trashes the file if it exists and does not have any parent trashed', async () => { const file = FileMother.any(); - repository.searchByPartialMock.mockReturnValueOnce(file); + repository.searchByContentsIdMock.mockReturnValueOnce(file); jest.spyOn(allParentFoldersStatusIsExists, 'run').mockReturnValueOnce(true); await SUT.run(file.contentsId); @@ -77,7 +77,7 @@ describe('File Deleter', () => { it('trashes the file with the status trashed', async () => { const file = FileMother.any(); - repository.searchByPartialMock.mockReturnValueOnce(file); + repository.searchByContentsIdMock.mockReturnValueOnce(file); jest.spyOn(allParentFoldersStatusIsExists, 'run').mockReturnValueOnce(true); await SUT.run(file.contentsId); diff --git a/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts b/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts index d5dd80aad..7b28ece04 100644 --- a/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts +++ b/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts @@ -41,7 +41,7 @@ describe('File path updater', () => { const fileToRename = FileMother.any(); const fileWithDestinationPath = undefined; - repository.searchByPartialMock + singleFileMatchingTestClass.mock .mockReturnValueOnce(fileToRename) .mockReturnValueOnce(fileWithDestinationPath); @@ -63,7 +63,7 @@ describe('File path updater', () => { const fileToRename = FileMother.any(); const fileWithDestinationPath = undefined; - repository.searchByPartialMock + singleFileMatchingTestClass.mock .mockReturnValueOnce(fileToRename) .mockReturnValueOnce(fileWithDestinationPath); @@ -81,7 +81,7 @@ describe('File path updater', () => { const fileInDestination = undefined; const localFileId = '1-2'; - repository.searchByPartialMock + singleFileMatchingTestClass.mock .mockReturnValueOnce(fileToMove) .mockReturnValueOnce(fileInDestination); From aae63063950e06922f5dc870cfee8089cc6ed78e Mon Sep 17 00:00:00 2001 From: joan vicens Date: Mon, 26 Feb 2024 17:42:42 +0100 Subject: [PATCH 11/15] chore: delete list by matching in file repo --- .../application/FilesByFolderPathSearcher.ts | 2 +- .../files/domain/FileRepository.ts | 4 +-- .../infrastructure/InMemoryFileRepository.ts | 28 +++---------------- .../files/__mocks__/FileRepositoryMock.ts | 8 +----- 4 files changed, 7 insertions(+), 35 deletions(-) diff --git a/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts b/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts index b23dc617e..823bc925f 100644 --- a/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts +++ b/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts @@ -12,7 +12,7 @@ export class FilesByFolderPathSearcher { async run(folderPath: string): Promise> { const folder = this.folderFinder.run(folderPath); - const files = await this.repository.listByPartial({ + const files = this.repository.matchingPartial({ folderId: folder.id, status: FileStatuses.EXISTS, }); diff --git a/src/context/virtual-drive/files/domain/FileRepository.ts b/src/context/virtual-drive/files/domain/FileRepository.ts index e79b1dfa7..ca5758196 100644 --- a/src/context/virtual-drive/files/domain/FileRepository.ts +++ b/src/context/virtual-drive/files/domain/FileRepository.ts @@ -3,14 +3,12 @@ import { File, FileAttributes } from './File'; export interface FileRepository { all(): Promise>; - matchingPartial(partial: Partial): Array | undefined; + matchingPartial(partial: Partial): Array; searchById(id: File['id']): Promise; searchByContentsId(id: File['contentsId']): Promise; - listByPartial(partial: Partial): Promise>; - delete(id: File['contentsId']): Promise; add(file: File): Promise; diff --git a/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts b/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts index 6e9abeab3..89d9c902a 100644 --- a/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts +++ b/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts @@ -46,7 +46,7 @@ export class InMemoryFileRepository implements FileRepository { return Promise.resolve(files); } - matchingPartial(partial: Partial): Array | undefined { + matchingPartial(partial: Partial): Array { const keys = Object.keys(partial) as Array>; const filesAttributes = this.values.filter((attributes) => { @@ -61,31 +61,11 @@ export class InMemoryFileRepository implements FileRepository { }); }); - if (filesAttributes) { - return filesAttributes.map((attributes) => File.from(attributes)); + if (!filesAttributes) { + return []; } - return undefined; - } - - listByPartial(partial: Partial): Promise> { - const keys = Object.keys(partial) as Array>; - - const filesAttributes = this.values.filter((attributes) => { - return keys.every((key: keyof FileAttributes) => { - if (key === 'contentsId') { - return ( - attributes[key].normalize() == (partial[key] as string).normalize() - ); - } - - return attributes[key] == partial[key]; - }); - }); - - const files = filesAttributes.map((attributes) => File.from(attributes)); - - return Promise.resolve(files); + return filesAttributes.map((attributes) => File.from(attributes)); } async delete(id: File['contentsId']): Promise { diff --git a/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts b/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts index d14b55686..0a2b7324c 100644 --- a/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts +++ b/tests/context/virtual-drive/files/__mocks__/FileRepositoryMock.ts @@ -1,4 +1,3 @@ -import { Nullable } from '../../../../../src/apps/shared/types/Nullable'; import { File, FileAttributes, @@ -10,7 +9,6 @@ export class FileRepositoryMock implements FileRepository { public readonly matchingPartialMock = jest.fn(); public readonly searchByIdMock = jest.fn(); public readonly searchByContentsIdMock = jest.fn(); - public readonly listByPartialMock = jest.fn(); public readonly deleteMock = jest.fn(); public readonly addMock = jest.fn(); public readonly updateMock = jest.fn(); @@ -19,14 +17,10 @@ export class FileRepositoryMock implements FileRepository { return this.allMock(); } - matchingPartial(partial: Partial): Nullable> { + matchingPartial(partial: Partial): Array { return this.matchingPartialMock(partial); } - listByPartial(partial: Partial): Promise { - return this.listByPartialMock(partial); - } - searchById(id: number): Promise { return this.searchByIdMock(id); } From 01d31008fe1c9d93673c139ccec4788a3646fd82 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 27 Feb 2024 11:55:45 +0100 Subject: [PATCH 12/15] feat: improve folder finders --- src/apps/fuse/callbacks/ReaddirCallback.ts | 7 +++-- .../fuse/callbacks/TrashFolderCallback.ts | 13 ++++---- .../virtual-drive/files/builder.ts | 6 ++-- .../virtual-drive/folders/FoldersContainer.ts | 8 ++--- .../virtual-drive/folders/builder.ts | 17 +++++----- .../dependency-injection/files/builder.ts | 2 +- .../folders/FoldersContainer.ts | 6 ++-- .../dependency-injection/folders/builder.ts | 12 +++---- .../controllers/AddController.ts | 4 ++- .../dependency-injection/files/builder.ts | 4 +-- .../folders/FoldersContainer.ts | 6 ++-- .../dependency-injection/folders/builder.ts | 16 ++++------ .../files/application/FileCreator.ts | 6 ++-- .../files/application/FilePathUpdater.ts | 6 ++-- .../application/FilesByFolderPathSearcher.ts | 9 +++--- .../AllParentFoldersStatusIsExists.ts | 4 +-- .../application/FolderByPartialSearcher.ts | 10 ------ .../folders/application/FolderCreator.ts | 28 +++++++---------- .../folders/application/FolderDeleter.ts | 2 +- .../folders/application/FolderFinder.ts | 28 ----------------- .../folders/application/FolderMover.ts | 14 ++++++--- .../folders/application/FolderPathUpdater.ts | 6 +++- .../folders/application/FolderSearcher.ts | 12 ------- .../application/FoldersByParentPathLister.ts | 11 ++++--- .../Offline/OfflineFolderCreator.ts | 10 +++--- .../application/Offline/OfflineFolderMover.ts | 6 ++-- .../folders/application/ParentFolderFinder.ts | 26 ++++++++++++++++ .../application/SingleFolderMatchingFinder.ts | 21 +++++++++++++ .../SynchronizeOfflineModifications.ts | 2 +- .../application/UpdatePlaceholderFolder.ts | 4 +-- .../virtual-drive/folders/domain/Folder.ts | 5 +++ .../folders/domain/FolderRepository.ts | 5 ++- .../domain/errors/FolderAlreadyTrashed.ts | 7 +++++ .../InMemoryFolderRepository.ts | 30 +++++++++++++----- .../files/application/FileCreator.test.ts | 4 +-- .../files/application/FileDeleter.test.ts | 12 ++++--- .../files/application/FilePathUpdater.test.ts | 10 +++--- .../folders/__mocks__/FolderFinderFactory.ts | 8 ++--- .../folders/__mocks__/FolderFinderMock.ts | 14 --------- .../folders/__mocks__/FolderMoverMock.ts | 8 +++-- .../folders/__mocks__/FolderRepositoryMock.ts | 16 ++++++++-- .../ParentFolderFinderTestClass.ts | 15 +++++++++ .../folders/application/FolderDeleter.test.ts | 26 ++++++++++------ .../folders/application/FolderMover.test.ts | 31 ++++++++++++------- .../application/FolderPathUpdater.test.ts | 10 +++--- .../SynchronizeOfflineModifications.test.ts | 14 ++++----- .../folders/domain/FolderMother.ts | 12 ------- 47 files changed, 291 insertions(+), 242 deletions(-) delete mode 100644 src/context/virtual-drive/folders/application/FolderByPartialSearcher.ts delete mode 100644 src/context/virtual-drive/folders/application/FolderFinder.ts delete mode 100644 src/context/virtual-drive/folders/application/FolderSearcher.ts create mode 100644 src/context/virtual-drive/folders/application/ParentFolderFinder.ts create mode 100644 src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts create mode 100644 src/context/virtual-drive/folders/domain/errors/FolderAlreadyTrashed.ts delete mode 100644 tests/context/virtual-drive/folders/__mocks__/FolderFinderMock.ts create mode 100644 tests/context/virtual-drive/folders/__test-class__/ParentFolderFinderTestClass.ts diff --git a/src/apps/fuse/callbacks/ReaddirCallback.ts b/src/apps/fuse/callbacks/ReaddirCallback.ts index c508190c2..f37bcd761 100644 --- a/src/apps/fuse/callbacks/ReaddirCallback.ts +++ b/src/apps/fuse/callbacks/ReaddirCallback.ts @@ -1,3 +1,4 @@ +import { FolderPath } from '../../../context/virtual-drive/folders/domain/FolderPath'; import { VirtualDriveDependencyContainer } from '../dependency-injection/virtual-drive/VirtualDriveDependencyContainer'; import { FuseCallback } from './FuseCallback'; @@ -7,11 +8,13 @@ export class ReaddirCallback extends FuseCallback> { } async execute(path: string) { + const folderPath = new FolderPath(path); + const filesNamesPromise = - this.container.filesByFolderPathNameLister.run(path); + this.container.filesByFolderPathNameLister.run(folderPath); const folderNamesPromise = - this.container.foldersByParentPathLister.run(path); + this.container.foldersByParentPathLister.run(folderPath); const [filesNames, foldersNames] = await Promise.all([ filesNamesPromise, diff --git a/src/apps/fuse/callbacks/TrashFolderCallback.ts b/src/apps/fuse/callbacks/TrashFolderCallback.ts index 007b5d39e..caaca0b35 100644 --- a/src/apps/fuse/callbacks/TrashFolderCallback.ts +++ b/src/apps/fuse/callbacks/TrashFolderCallback.ts @@ -1,6 +1,6 @@ +import { FolderStatuses } from '../../../context/virtual-drive/folders/domain/FolderStatus'; import { VirtualDriveDependencyContainer } from '../dependency-injection/virtual-drive/VirtualDriveDependencyContainer'; import { NotifyFuseCallback } from './FuseCallback'; -import { FuseNoSuchFileOrDirectoryError } from './FuseErrors'; import { basename } from 'path'; export class TrashFolderCallback extends NotifyFuseCallback { @@ -9,13 +9,12 @@ export class TrashFolderCallback extends NotifyFuseCallback { } async execute(path: string) { - const folder = await this.container.folderSearcher.run({ path }); - - if (!folder) { - return this.left(new FuseNoSuchFileOrDirectoryError()); - } - try { + const folder = await this.container.singleFolderMatchingFinder.run({ + path, + status: FolderStatuses.EXISTS, + }); + await this.container.folderDeleter.run(folder.uuid); return this.right(); diff --git a/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts b/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts index 2042d7679..cacdc3daf 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/files/builder.ts @@ -42,7 +42,7 @@ export async function buildFilesContainer( const filesByFolderPathNameLister = new FilesByFolderPathSearcher( repository, - folderContainer.folderFinder + folderContainer.parentFolderFinder ); const filesSearcher = new FirstsFileSearcher(repository); @@ -64,7 +64,7 @@ export async function buildFilesContainer( localFileSystem, repository, singleFileMatchingSearcher, - folderContainer.folderFinder, + folderContainer.parentFolderFinder, eventBus ); @@ -85,7 +85,7 @@ export async function buildFilesContainer( const fileCreator = new FileCreator( remoteFileSystem, repository, - folderContainer.folderFinder, + folderContainer.parentFolderFinder, fileDeleter, eventBus, syncFileMessenger diff --git a/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts b/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts index 96f552530..1ac6db7e3 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts @@ -2,16 +2,15 @@ import { AllParentFoldersStatusIsExists } from '../../../../../context/virtual-d import { FolderCreator } from '../../../../../context/virtual-drive/folders/application/FolderCreator'; import { FolderCreatorFromOfflineFolder } from '../../../../../context/virtual-drive/folders/application/FolderCreatorFromOfflineFolder'; import { FolderDeleter } from '../../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { FolderFinder } from '../../../../../context/virtual-drive/folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { FolderPathUpdater } from '../../../../../context/virtual-drive/folders/application/FolderPathUpdater'; import { FolderRepositoryInitializer } from '../../../../../context/virtual-drive/folders/application/FolderRepositoryInitializer'; -import { FolderSearcher } from '../../../../../context/virtual-drive/folders/application/FolderSearcher'; import { FoldersByParentPathLister } from '../../../../../context/virtual-drive/folders/application/FoldersByParentPathLister'; import { SyncFolderMessenger } from '../../../../../context/virtual-drive/folders/domain/SyncFolderMessenger'; +import { SingleFolderMatchingFinder } from '../../../../../context/virtual-drive/folders/application/SingleFolderMatchingFinder'; export interface FoldersContainer { - folderFinder: FolderFinder; - folderSearcher: FolderSearcher; + parentFolderFinder: ParentFolderFinder; foldersByParentPathLister: FoldersByParentPathLister; folderPathUpdater: FolderPathUpdater; allParentFoldersStatusIsExists: AllParentFoldersStatusIsExists; @@ -20,4 +19,5 @@ export interface FoldersContainer { folderDeleter: FolderDeleter; folderRepositoryInitiator: FolderRepositoryInitializer; syncFolderMessenger: SyncFolderMessenger; + singleFolderMatchingFinder: SingleFolderMatchingFinder; } diff --git a/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts b/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts index 7cf00a97f..d0df5776d 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts @@ -2,12 +2,11 @@ import { AllParentFoldersStatusIsExists } from '../../../../../context/virtual-d import { FolderCreator } from '../../../../../context/virtual-drive/folders/application/FolderCreator'; import { FolderCreatorFromOfflineFolder } from '../../../../../context/virtual-drive/folders/application/FolderCreatorFromOfflineFolder'; import { FolderDeleter } from '../../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { FolderFinder } from '../../../../../context/virtual-drive/folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { FolderMover } from '../../../../../context/virtual-drive/folders/application/FolderMover'; import { FolderPathUpdater } from '../../../../../context/virtual-drive/folders/application/FolderPathUpdater'; import { FolderRenamer } from '../../../../../context/virtual-drive/folders/application/FolderRenamer'; import { FolderRepositoryInitializer } from '../../../../../context/virtual-drive/folders/application/FolderRepositoryInitializer'; -import { FolderSearcher } from '../../../../../context/virtual-drive/folders/application/FolderSearcher'; import { FoldersByParentPathLister } from '../../../../../context/virtual-drive/folders/application/FoldersByParentPathLister'; import { Folder } from '../../../../../context/virtual-drive/folders/domain/Folder'; import { FuseLocalFileSystem } from '../../../../../context/virtual-drive/folders/infrastructure/FuseLocalFileSystem'; @@ -18,6 +17,7 @@ import { DependencyInjectionHttpClientsProvider } from '../../common/clients'; import { DependencyInjectionEventBus } from '../../common/eventBus'; import { FoldersContainer } from './FoldersContainer'; +import { SingleFolderMatchingFinder } from '../../../../../context/virtual-drive/folders/application/SingleFolderMatchingFinder'; export async function buildFoldersContainer( initialFolders: Array @@ -41,19 +41,18 @@ export async function buildFoldersContainer( await folderRepositoryInitiator.run(initialFolders); - const folderFinder = new FolderFinder(repository); - - const folderSearcher = new FolderSearcher(repository); + const parentFolderFinder = new ParentFolderFinder(repository); + const singleFolderMatchingFinder = new SingleFolderMatchingFinder(repository); const foldersByParentPathSearcher = new FoldersByParentPathLister( - folderFinder, + parentFolderFinder, repository ); const folderMover = new FolderMover( repository, remoteFileSystem, - folderFinder + parentFolderFinder ); const folderRenamer = new FolderRenamer( @@ -82,6 +81,7 @@ export async function buildFoldersContainer( const folderCreator = new FolderCreator( repository, + singleFolderMatchingFinder, remoteFileSystem, eventBus ); @@ -94,8 +94,7 @@ export async function buildFoldersContainer( ); return { - folderFinder, - folderSearcher, + parentFolderFinder, foldersByParentPathLister: foldersByParentPathSearcher, folderPathUpdater, allParentFoldersStatusIsExists, diff --git a/src/apps/hydration-api/dependency-injection/files/builder.ts b/src/apps/hydration-api/dependency-injection/files/builder.ts index 8d8a00caa..3ee93c161 100644 --- a/src/apps/hydration-api/dependency-injection/files/builder.ts +++ b/src/apps/hydration-api/dependency-injection/files/builder.ts @@ -12,7 +12,7 @@ export async function buildFilesContainer( const filesByFolderPathNameLister = new FilesByFolderPathSearcher( repository, - folderContainer.folderFinder + folderContainer.parentFolderFinder ); const filesSearcher = new FirstsFileSearcher(repository); diff --git a/src/apps/hydration-api/dependency-injection/folders/FoldersContainer.ts b/src/apps/hydration-api/dependency-injection/folders/FoldersContainer.ts index 6dd4b46a7..e08ff0b4a 100644 --- a/src/apps/hydration-api/dependency-injection/folders/FoldersContainer.ts +++ b/src/apps/hydration-api/dependency-injection/folders/FoldersContainer.ts @@ -1,9 +1,7 @@ -import { FolderFinder } from '../../../../context/virtual-drive/folders/application/FolderFinder'; -import { FolderSearcher } from '../../../../context/virtual-drive/folders/application/FolderSearcher'; +import { ParentFolderFinder } from '../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { FoldersByParentPathLister } from '../../../../context/virtual-drive/folders/application/FoldersByParentPathLister'; export interface FoldersContainer { - folderFinder: FolderFinder; - folderSearcher: FolderSearcher; + parentFolderFinder: ParentFolderFinder; foldersByParentPathSearcher: FoldersByParentPathLister; } diff --git a/src/apps/hydration-api/dependency-injection/folders/builder.ts b/src/apps/hydration-api/dependency-injection/folders/builder.ts index d52c66964..c9247748d 100644 --- a/src/apps/hydration-api/dependency-injection/folders/builder.ts +++ b/src/apps/hydration-api/dependency-injection/folders/builder.ts @@ -1,5 +1,4 @@ -import { FolderFinder } from '../../../../context/virtual-drive/folders/application/FolderFinder'; -import { FolderSearcher } from '../../../../context/virtual-drive/folders/application/FolderSearcher'; +import { ParentFolderFinder } from '../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { FoldersByParentPathLister } from '../../../../context/virtual-drive/folders/application/FoldersByParentPathLister'; import { InMemoryFolderRepository } from '../../../../context/virtual-drive/folders/infrastructure/InMemoryFolderRepository'; import { FoldersContainer } from './FoldersContainer'; @@ -7,18 +6,15 @@ import { FoldersContainer } from './FoldersContainer'; export async function buildFoldersContainer(): Promise { const repository = new InMemoryFolderRepository(); - const folderFinder = new FolderFinder(repository); - - const folderSearcher = new FolderSearcher(repository); + const parentFolderFinder = new ParentFolderFinder(repository); const foldersByParentPathSearcher = new FoldersByParentPathLister( - folderFinder, + parentFolderFinder, repository ); return { - folderFinder, - folderSearcher, + parentFolderFinder, foldersByParentPathSearcher, }; } diff --git a/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts index f16a5930d..48c766477 100644 --- a/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -80,7 +80,9 @@ export class AddController extends CallbackController { } }; private async runFolderCreator(posixRelativePath: string): Promise { - const offlineFolder = this.offlineFolderCreator.run(posixRelativePath); + const offlineFolder = await this.offlineFolderCreator.run( + posixRelativePath + ); return this.folderCreator.run(offlineFolder); } diff --git a/src/apps/sync-engine/dependency-injection/files/builder.ts b/src/apps/sync-engine/dependency-injection/files/builder.ts index 1f7f1add1..885f45c3a 100644 --- a/src/apps/sync-engine/dependency-injection/files/builder.ts +++ b/src/apps/sync-engine/dependency-injection/files/builder.ts @@ -81,14 +81,14 @@ export async function buildFilesContainer( localFileSystem, repository, singleFileMatchingSearcher, - folderContainer.folderFinder, + folderContainer.parentFolderFinder, eventBus ); const fileCreator = new FileCreator( remoteFileSystem, repository, - folderContainer.folderFinder, + folderContainer.parentFolderFinder, fileDeleter, eventBus, syncFileMessenger diff --git a/src/apps/sync-engine/dependency-injection/folders/FoldersContainer.ts b/src/apps/sync-engine/dependency-injection/folders/FoldersContainer.ts index 623806105..127348b21 100644 --- a/src/apps/sync-engine/dependency-injection/folders/FoldersContainer.ts +++ b/src/apps/sync-engine/dependency-injection/folders/FoldersContainer.ts @@ -1,8 +1,7 @@ import { AllParentFoldersStatusIsExists } from '../../../../context/virtual-drive/folders/application/AllParentFoldersStatusIsExists'; -import { FolderByPartialSearcher } from '../../../../context/virtual-drive/folders/application/FolderByPartialSearcher'; import { FolderCreatorFromOfflineFolder } from '../../../../context/virtual-drive/folders/application/FolderCreatorFromOfflineFolder'; import { FolderDeleter } from '../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { FolderFinder } from '../../../../context/virtual-drive/folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { FolderPathUpdater } from '../../../../context/virtual-drive/folders/application/FolderPathUpdater'; import { FolderRepositoryInitializer } from '../../../../context/virtual-drive/folders/application/FolderRepositoryInitializer'; import { FoldersPlaceholderCreator } from '../../../../context/virtual-drive/folders/application/FoldersPlaceholderCreator'; @@ -15,11 +14,10 @@ import { FolderPlaceholderUpdater } from '../../../../context/virtual-drive/fold export interface FoldersContainer { folderCreator: FolderCreatorFromOfflineFolder; - folderFinder: FolderFinder; + parentFolderFinder: ParentFolderFinder; folderDeleter: FolderDeleter; allParentFoldersStatusIsExists: AllParentFoldersStatusIsExists; folderPathUpdater: FolderPathUpdater; - folderByPartialSearcher: FolderByPartialSearcher; synchronizeOfflineModificationsOnFolderCreated: SynchronizeOfflineModificationsOnFolderCreated; offline: { folderCreator: OfflineFolderCreator; diff --git a/src/apps/sync-engine/dependency-injection/folders/builder.ts b/src/apps/sync-engine/dependency-injection/folders/builder.ts index 0534aa5ff..295d11f83 100644 --- a/src/apps/sync-engine/dependency-injection/folders/builder.ts +++ b/src/apps/sync-engine/dependency-injection/folders/builder.ts @@ -1,8 +1,7 @@ import { AllParentFoldersStatusIsExists } from '../../../../context/virtual-drive/folders/application/AllParentFoldersStatusIsExists'; -import { FolderByPartialSearcher } from '../../../../context/virtual-drive/folders/application/FolderByPartialSearcher'; import { FolderCreatorFromOfflineFolder } from '../../../../context/virtual-drive/folders/application/FolderCreatorFromOfflineFolder'; import { FolderDeleter } from '../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { FolderFinder } from '../../../../context/virtual-drive/folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { FolderMover } from '../../../../context/virtual-drive/folders/application/FolderMover'; import { FolderPathUpdater } from '../../../../context/virtual-drive/folders/application/FolderPathUpdater'; import { FolderRenamer } from '../../../../context/virtual-drive/folders/application/FolderRenamer'; @@ -50,7 +49,7 @@ export async function buildFoldersContainer( clients.newDrive ); - const folderFinder = new FolderFinder(repository); + const parentFolderFinder = new ParentFolderFinder(repository); const allParentFoldersStatusIsExists = new AllParentFoldersStatusIsExists( repository @@ -73,7 +72,7 @@ export async function buildFoldersContainer( const folderMover = new FolderMover( repository, remoteFileSystem, - folderFinder + parentFolderFinder ); const folderRenamer = new FolderRenamer( repository, @@ -82,8 +81,6 @@ export async function buildFoldersContainer( syncFolderMessenger ); - const folderByPartialSearcher = new FolderByPartialSearcher(repository); - const folderPathUpdater = new FolderPathUpdater( repository, folderMover, @@ -92,14 +89,14 @@ export async function buildFoldersContainer( const offlineRepository = new InMemoryOfflineFolderRepository(); const offlineFolderCreator = new OfflineFolderCreator( - folderFinder, + parentFolderFinder, offlineRepository, repository ); const offlineFolderMover = new OfflineFolderMover( offlineRepository, - folderFinder + parentFolderFinder ); const offlineFolderRenamer = new OfflineFolderRenamer(offlineRepository); const offlineFolderPathUpdater = new OfflineFolderPathUpdater( @@ -133,11 +130,10 @@ export async function buildFoldersContainer( return { folderCreator, - folderFinder, + parentFolderFinder, folderDeleter, allParentFoldersStatusIsExists: allParentFoldersStatusIsExists, folderPathUpdater, - folderByPartialSearcher, synchronizeOfflineModificationsOnFolderCreated, offline: { folderCreator: offlineFolderCreator, diff --git a/src/context/virtual-drive/files/application/FileCreator.ts b/src/context/virtual-drive/files/application/FileCreator.ts index 31b647e2c..b6b7e5220 100644 --- a/src/context/virtual-drive/files/application/FileCreator.ts +++ b/src/context/virtual-drive/files/application/FileCreator.ts @@ -1,4 +1,3 @@ -import { FolderFinder } from '../../folders/application/FolderFinder'; import { FilePath } from '../domain/FilePath'; import { File } from '../domain/File'; import { FileSize } from '../domain/FileSize'; @@ -12,12 +11,13 @@ import { SyncFileMessenger } from '../domain/SyncFileMessenger'; import { FileStatuses } from '../domain/FileStatus'; import { DriveDesktopError } from '../../../shared/domain/errors/DriveDesktopError'; import Logger from 'electron-log'; +import { ParentFolderFinder } from '../../folders/application/ParentFolderFinder'; export class FileCreator { constructor( private readonly remote: RemoteFileSystem, private readonly repository: FileRepository, - private readonly folderFinder: FolderFinder, + private readonly parentFolderFinder: ParentFolderFinder, private readonly fileDeleter: FileDeleter, private readonly eventBus: EventBus, private readonly notifier: SyncFileMessenger @@ -44,7 +44,7 @@ export class FileCreator { const fileSize = new FileSize(size); - const folder = this.folderFinder.findFromFilePath(filePath); + const folder = await this.parentFolderFinder.run(filePath); const offline = OfflineFile.create( contentsId, diff --git a/src/context/virtual-drive/files/application/FilePathUpdater.ts b/src/context/virtual-drive/files/application/FilePathUpdater.ts index bb588ce78..018bd459e 100644 --- a/src/context/virtual-drive/files/application/FilePathUpdater.ts +++ b/src/context/virtual-drive/files/application/FilePathUpdater.ts @@ -1,5 +1,5 @@ import Logger from 'electron-log'; -import { FolderFinder } from '../../folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../folders/application/ParentFolderFinder'; import { EventBus } from '../../shared/domain/EventBus'; import { File } from '../domain/File'; import { FilePath } from '../domain/FilePath'; @@ -20,7 +20,7 @@ export class FilePathUpdater { private readonly local: LocalFileSystem, private readonly repository: FileRepository, private readonly singleFileMatching: SingleFileMatchingSearcher, - private readonly folderFinder: FolderFinder, + private readonly parentFolderFinder: ParentFolderFinder, private readonly eventBus: EventBus ) {} @@ -45,7 +45,7 @@ export class FilePathUpdater { private async move(file: File, destination: FilePath) { const trackerId = await this.local.getLocalFileId(file); - const destinationFolder = this.folderFinder.run(destination.dirname()); + const destinationFolder = await this.parentFolderFinder.run(destination); file.moveTo(destinationFolder, trackerId); diff --git a/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts b/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts index 823bc925f..9495338bc 100644 --- a/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts +++ b/src/context/virtual-drive/files/application/FilesByFolderPathSearcher.ts @@ -1,4 +1,5 @@ -import { FolderFinder } from '../../folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../folders/application/ParentFolderFinder'; +import { FolderPath } from '../../folders/domain/FolderPath'; import { File } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; import { FileStatuses } from '../domain/FileStatus'; @@ -6,11 +7,11 @@ import { FileStatuses } from '../domain/FileStatus'; export class FilesByFolderPathSearcher { constructor( private readonly repository: FileRepository, - private readonly folderFinder: FolderFinder + private readonly parentFolderFinder: ParentFolderFinder ) {} - async run(folderPath: string): Promise> { - const folder = this.folderFinder.run(folderPath); + async run(folderPath: FolderPath): Promise> { + const folder = await this.parentFolderFinder.run(folderPath); const files = this.repository.matchingPartial({ folderId: folder.id, diff --git a/src/context/virtual-drive/folders/application/AllParentFoldersStatusIsExists.ts b/src/context/virtual-drive/folders/application/AllParentFoldersStatusIsExists.ts index fe5e1229c..65952a3ac 100644 --- a/src/context/virtual-drive/folders/application/AllParentFoldersStatusIsExists.ts +++ b/src/context/virtual-drive/folders/application/AllParentFoldersStatusIsExists.ts @@ -5,8 +5,8 @@ import { FolderStatuses } from '../domain/FolderStatus'; export class AllParentFoldersStatusIsExists { constructor(private readonly repository: FolderRepository) {} - run(id: Folder['id']): boolean { - const folder = this.repository.searchByPartial({ id }); + async run(id: Folder['id']): Promise { + const folder = await this.repository.searchById(id); if (!folder) { // TODO: investigate why when uploading a file in a path than previously existed returns undefined diff --git a/src/context/virtual-drive/folders/application/FolderByPartialSearcher.ts b/src/context/virtual-drive/folders/application/FolderByPartialSearcher.ts deleted file mode 100644 index 634d4573a..000000000 --- a/src/context/virtual-drive/folders/application/FolderByPartialSearcher.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Folder, FolderAttributes } from '../domain/Folder'; -import { FolderRepository } from '../domain/FolderRepository'; - -export class FolderByPartialSearcher { - constructor(private readonly repository: FolderRepository) {} - - run(partial: Partial): Folder | undefined { - return this.repository.searchByPartial(partial); - } -} diff --git a/src/context/virtual-drive/folders/application/FolderCreator.ts b/src/context/virtual-drive/folders/application/FolderCreator.ts index c7874bf5e..5b19530e1 100644 --- a/src/context/virtual-drive/folders/application/FolderCreator.ts +++ b/src/context/virtual-drive/folders/application/FolderCreator.ts @@ -4,38 +4,34 @@ import { FolderCreatedAt } from '../domain/FolderCreatedAt'; import { FolderId } from '../domain/FolderId'; import { FolderPath } from '../domain/FolderPath'; import { FolderRepository } from '../domain/FolderRepository'; +import { FolderStatuses } from '../domain/FolderStatus'; import { FolderUpdatedAt } from '../domain/FolderUpdatedAt'; import { FolderUuid } from '../domain/FolderUuid'; import { FolderAlreadyExists } from '../domain/errors/FolderAlreadyExists'; -import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; import { RemoteFileSystem } from '../domain/file-systems/RemoteFileSystem'; +import { SingleFolderMatchingFinder } from './SingleFolderMatchingFinder'; export class FolderCreator { constructor( private readonly repository: FolderRepository, + private readonly singleFolderFinder: SingleFolderMatchingFinder, private readonly remote: RemoteFileSystem, private readonly eventBus: EventBus ) {} private ensureItDoesNotExists(path: FolderPath): void { - const folder = this.repository.searchByPartial({ path: path.value }); - - if (!folder) { - return; - } - - throw new FolderAlreadyExists(folder); - } - - private findParentId(path: FolderPath): FolderId { - const parent = this.repository.searchByPartial({ - path: path.dirname(), + const folder = this.repository.matchingPartial({ + path: path.value, + status: FolderStatuses.EXISTS, }); - if (!parent) { - throw new FolderNotFoundError(path.dirname()); + if (folder.length > 0) { + throw new FolderAlreadyExists(folder[0]); } + } + private async findParentId(path: FolderPath): Promise { + const parent = await this.singleFolderFinder.run({ path: path.value }); return new FolderId(parent.id); } @@ -44,7 +40,7 @@ export class FolderCreator { this.ensureItDoesNotExists(folderPath); - const parentId = this.findParentId(folderPath); + const parentId = await this.findParentId(folderPath); const response = await this.remote.persist(folderPath, parentId); diff --git a/src/context/virtual-drive/folders/application/FolderDeleter.ts b/src/context/virtual-drive/folders/application/FolderDeleter.ts index 67cabbfc3..b4d0dffef 100644 --- a/src/context/virtual-drive/folders/application/FolderDeleter.ts +++ b/src/context/virtual-drive/folders/application/FolderDeleter.ts @@ -16,7 +16,7 @@ export class FolderDeleter { ) {} async run(uuid: Folder['uuid']): Promise { - const folder = this.repository.searchByPartial({ uuid }); + const folder = await this.repository.searchByUuid(uuid); if (!folder) { throw new FolderNotFoundError(uuid); diff --git a/src/context/virtual-drive/folders/application/FolderFinder.ts b/src/context/virtual-drive/folders/application/FolderFinder.ts deleted file mode 100644 index 8f50d9b46..000000000 --- a/src/context/virtual-drive/folders/application/FolderFinder.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { FilePath } from '../../files/domain/FilePath'; -import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; -import { Folder } from '../domain/Folder'; -import { FolderRepository } from '../domain/FolderRepository'; - -export class FolderFinder { - constructor(private readonly repository: FolderRepository) {} - - run(path: string): Folder { - const folder = this.repository.searchByPartial({ path }); - - if (!folder) { - throw new FolderNotFoundError(path); - } - - return folder; - } - - findFromFilePath(path: FilePath): Folder { - const folder = this.repository.searchByPartial({ path: path.dirname() }); - - if (!folder) { - throw new FolderNotFoundError(path.dirname()); - } - - return folder; - } -} diff --git a/src/context/virtual-drive/folders/application/FolderMover.ts b/src/context/virtual-drive/folders/application/FolderMover.ts index f185e9fd8..20c0bc287 100644 --- a/src/context/virtual-drive/folders/application/FolderMover.ts +++ b/src/context/virtual-drive/folders/application/FolderMover.ts @@ -1,15 +1,16 @@ import { ActionNotPermittedError } from '../domain/errors/ActionNotPermittedError'; import { FolderPath } from '../domain/FolderPath'; import { Folder } from '../domain/Folder'; -import { FolderFinder } from './FolderFinder'; +import { ParentFolderFinder } from './ParentFolderFinder'; import { FolderRepository } from '../domain/FolderRepository'; import { RemoteFileSystem } from '../domain/file-systems/RemoteFileSystem'; +import { FolderStatuses } from '../domain/FolderStatus'; export class FolderMover { constructor( private readonly repository: FolderRepository, private readonly remote: RemoteFileSystem, - private readonly folderFinder: FolderFinder + private readonly fileParentFolderFinder: ParentFolderFinder ) {} private async move(folder: Folder, parentFolder: Folder) { @@ -20,17 +21,20 @@ export class FolderMover { } async run(folder: Folder, destination: FolderPath): Promise { - const resultFolder = this.repository.searchByPartial({ + const resultFolder = this.repository.matchingPartial({ path: destination.value, + status: FolderStatuses.EXISTS, }); - const shouldBeMerge = resultFolder !== undefined; + const shouldBeMerge = resultFolder.length > 0; if (shouldBeMerge) { throw new ActionNotPermittedError('overwrite'); } - const destinationFolder = this.folderFinder.run(destination.dirname()); + const destinationFolder = await this.fileParentFolderFinder.run( + destination + ); await this.move(folder, destinationFolder); } diff --git a/src/context/virtual-drive/folders/application/FolderPathUpdater.ts b/src/context/virtual-drive/folders/application/FolderPathUpdater.ts index 5245f593b..9487a0a3c 100644 --- a/src/context/virtual-drive/folders/application/FolderPathUpdater.ts +++ b/src/context/virtual-drive/folders/application/FolderPathUpdater.ts @@ -7,6 +7,7 @@ import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; import { FolderMover } from './FolderMover'; import { FolderRenamer } from './FolderRenamer'; import { PathHasNotChangedError } from '../domain/errors/PathHasNotChangedError'; +import { FolderStatuses } from '../domain/FolderStatus'; export class FolderPathUpdater { constructor( @@ -16,7 +17,10 @@ export class FolderPathUpdater { ) {} async run(uuid: Folder['uuid'], posixRelativePath: string) { - const folder = this.repository.searchByPartial({ uuid }); + const folder = this.repository.matchingPartial({ + uuid, + status: FolderStatuses.EXISTS, + })[0]; if (!folder) { throw new FolderNotFoundError(uuid); diff --git a/src/context/virtual-drive/folders/application/FolderSearcher.ts b/src/context/virtual-drive/folders/application/FolderSearcher.ts deleted file mode 100644 index 924a65cb1..000000000 --- a/src/context/virtual-drive/folders/application/FolderSearcher.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Folder, FolderAttributes } from '../domain/Folder'; -import { FolderRepository } from '../domain/FolderRepository'; - -export class FolderSearcher { - constructor(private readonly repository: FolderRepository) {} - - run(partial: Partial): Promise { - const folder = this.repository.searchByPartial(partial); - - return Promise.resolve(folder); - } -} diff --git a/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts b/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts index dee1ba466..9de21c596 100644 --- a/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts +++ b/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts @@ -1,18 +1,19 @@ import { Folder } from '../domain/Folder'; +import { FolderPath } from '../domain/FolderPath'; import { FolderRepository } from '../domain/FolderRepository'; -import { FolderFinder } from './FolderFinder'; +import { ParentFolderFinder } from './ParentFolderFinder'; export class FoldersByParentPathLister { constructor( - private readonly folderFinder: FolderFinder, + private readonly parentFolderFinder: ParentFolderFinder, private readonly repository: FolderRepository ) {} - async run(path: string): Promise> { - const parent = this.folderFinder.run(path); + async run(folderPath: FolderPath): Promise> { + const parent = await this.parentFolderFinder.run(folderPath); const folders = await this.repository.listByPartial({ - parentId: parent?.id, + parentId: parent.id, }); return folders.map((folder) => folder.name); diff --git a/src/context/virtual-drive/folders/application/Offline/OfflineFolderCreator.ts b/src/context/virtual-drive/folders/application/Offline/OfflineFolderCreator.ts index e1bfad328..5b2638a22 100644 --- a/src/context/virtual-drive/folders/application/Offline/OfflineFolderCreator.ts +++ b/src/context/virtual-drive/folders/application/Offline/OfflineFolderCreator.ts @@ -1,21 +1,21 @@ import { FolderPath } from '../../domain/FolderPath'; import { OfflineFolder } from '../../domain/OfflineFolder'; import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; -import { FolderFinder } from '../FolderFinder'; +import { ParentFolderFinder } from '../ParentFolderFinder'; import { FolderRepository } from '../../domain/FolderRepository'; import { FolderId } from '../../domain/FolderId'; export class OfflineFolderCreator { constructor( - private readonly folderFinder: FolderFinder, + private readonly parentFolderFinder: ParentFolderFinder, private readonly offlineRepository: OfflineFolderRepository, private readonly repository: FolderRepository ) {} - run(posixRelativePath: string): OfflineFolder { + async run(posixRelativePath: string): Promise { const folderPath = new FolderPath(posixRelativePath); - const onlineFolder = this.repository.searchByPartial({ + const onlineFolder = this.repository.matchingPartial({ path: folderPath.value, }); @@ -23,7 +23,7 @@ export class OfflineFolderCreator { throw new Error('The folder already exists on remote'); } - const parent = this.folderFinder.run(folderPath.dirname()); + const parent = await this.parentFolderFinder.run(folderPath); const parentId = new FolderId(parent.id); diff --git a/src/context/virtual-drive/folders/application/Offline/OfflineFolderMover.ts b/src/context/virtual-drive/folders/application/Offline/OfflineFolderMover.ts index 21694eeeb..ad1c3e32d 100644 --- a/src/context/virtual-drive/folders/application/Offline/OfflineFolderMover.ts +++ b/src/context/virtual-drive/folders/application/Offline/OfflineFolderMover.ts @@ -2,12 +2,12 @@ import { FolderPath } from '../../domain/FolderPath'; import { OfflineFolder } from '../../domain/OfflineFolder'; import { OfflineFolderRepository } from '../../domain/OfflineFolderRepository'; import { ActionNotPermittedError } from '../../domain/errors/ActionNotPermittedError'; -import { FolderFinder } from '../FolderFinder'; +import { ParentFolderFinder } from '../ParentFolderFinder'; export class OfflineFolderMover { constructor( private readonly offlineFolderRepository: OfflineFolderRepository, - private readonly folderFinder: FolderFinder + private readonly parentFolderFinder: ParentFolderFinder ) {} async run(folder: OfflineFolder, destination: FolderPath) { @@ -21,7 +21,7 @@ export class OfflineFolderMover { throw new ActionNotPermittedError('overwrite'); } - const destinationFolder = this.folderFinder.run(destination.dirname()); + const destinationFolder = await this.parentFolderFinder.run(destination); folder.moveTo(destinationFolder); this.offlineFolderRepository.update(folder); diff --git a/src/context/virtual-drive/folders/application/ParentFolderFinder.ts b/src/context/virtual-drive/folders/application/ParentFolderFinder.ts new file mode 100644 index 000000000..e2e35c7e3 --- /dev/null +++ b/src/context/virtual-drive/folders/application/ParentFolderFinder.ts @@ -0,0 +1,26 @@ +import { Path } from '../../../shared/domain/value-objects/Path'; +import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; +import { Folder } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; +import { FolderStatuses } from '../domain/FolderStatus'; + +export class ParentFolderFinder { + constructor(private readonly repository: FolderRepository) {} + + async run(path: Path): Promise { + const result = this.repository.matchingPartial({ + path: path.dirname(), + status: FolderStatuses.EXISTS, + }); + + if (!result || result.length === 0) { + throw new FolderNotFoundError(path.dirname()); + } + + if (result.length > 1) { + throw new Error('A file can only have a parent'); + } + + return result[0]; + } +} diff --git a/src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts b/src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts new file mode 100644 index 000000000..b543f48cb --- /dev/null +++ b/src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts @@ -0,0 +1,21 @@ +import { Folder, FolderAttributes } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; +import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; + +export class SingleFolderMatchingFinder { + constructor(private readonly repository: FolderRepository) {} + + async run(partial: Partial): Promise { + const folders = this.repository.matchingPartial(partial); + + if (folders.length === 0) { + throw new FolderNotFoundError(JSON.stringify(partial)); + } + + if (folders.length > 1) { + throw new Error('Expected to find a singular folder'); + } + + return folders[0]; + } +} diff --git a/src/context/virtual-drive/folders/application/SynchronizeOfflineModifications.ts b/src/context/virtual-drive/folders/application/SynchronizeOfflineModifications.ts index fd5818b5a..85447c4b0 100644 --- a/src/context/virtual-drive/folders/application/SynchronizeOfflineModifications.ts +++ b/src/context/virtual-drive/folders/application/SynchronizeOfflineModifications.ts @@ -35,7 +35,7 @@ export class SynchronizeOfflineModifications { const rename = event as FolderRenamedDomainEvent; - const folder = this.repository.searchByPartial({ uuid }); + const folder = this.repository.matchingPartial({ uuid })[0]; if (!folder) { throw new FolderNotFoundError(uuid); diff --git a/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts b/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts index 9b06bd1b7..d665cd148 100644 --- a/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts +++ b/src/context/virtual-drive/folders/application/UpdatePlaceholderFolder.ts @@ -78,9 +78,9 @@ export class FolderPlaceholderUpdater { return; } - const local = this.repository.searchByPartial({ + const local = this.repository.matchingPartial({ uuid: remote.uuid, - }); + })[0]; if (!local) { if (remote.hasStatus(FolderStatuses.EXISTS)) { diff --git a/src/context/virtual-drive/folders/domain/Folder.ts b/src/context/virtual-drive/folders/domain/Folder.ts index 8c5001bb2..264f37c5b 100644 --- a/src/context/virtual-drive/folders/domain/Folder.ts +++ b/src/context/virtual-drive/folders/domain/Folder.ts @@ -9,6 +9,7 @@ import { FolderId } from './FolderId'; import { FolderCreatedAt } from './FolderCreatedAt'; import { FolderUpdatedAt } from './FolderUpdatedAt'; import { FolderMovedDomainEvent } from './events/FolderMovedDomainEvent'; +import { FolderAlreadyTrashed } from './errors/FolderAlreadyTrashed'; export type FolderAttributes = { id: number; @@ -185,6 +186,10 @@ export class Folder extends AggregateRoot { } trash() { + if (!this._status.is(FolderStatuses.EXISTS)) { + throw new FolderAlreadyTrashed(this.name); + } + this._status = this._status.changeTo(FolderStatuses.TRASHED); this._updatedAt = FolderUpdatedAt.now(); diff --git a/src/context/virtual-drive/folders/domain/FolderRepository.ts b/src/context/virtual-drive/folders/domain/FolderRepository.ts index fb27c56d0..cafd8cfed 100644 --- a/src/context/virtual-drive/folders/domain/FolderRepository.ts +++ b/src/context/virtual-drive/folders/domain/FolderRepository.ts @@ -3,7 +3,10 @@ import { Folder, FolderAttributes } from './Folder'; export interface FolderRepository { all(): Promise>; - searchByPartial(partial: Partial): Folder | undefined; + searchById(id: Folder['id']): Promise; + searchByUuid(id: Folder['uuid']): Promise; + + matchingPartial(partial: Partial): Array; listByPartial(partial: Partial): Promise>; diff --git a/src/context/virtual-drive/folders/domain/errors/FolderAlreadyTrashed.ts b/src/context/virtual-drive/folders/domain/errors/FolderAlreadyTrashed.ts new file mode 100644 index 000000000..37d61b2c4 --- /dev/null +++ b/src/context/virtual-drive/folders/domain/errors/FolderAlreadyTrashed.ts @@ -0,0 +1,7 @@ +import { DriveDesktopError } from '../../../../shared/domain/errors/DriveDesktopError'; + +export class FolderAlreadyTrashed extends DriveDesktopError { + constructor(name: string) { + super('ACTION_NOT_PERMITTED', `Folder ${name} is already in the trash`); + } +} diff --git a/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts b/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts index e2e2c8a28..325ee16be 100644 --- a/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts +++ b/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts @@ -20,20 +20,36 @@ export class InMemoryFolderRepository implements FolderRepository { return Promise.resolve(folders); } - searchByPartial(partial: Partial): Folder | undefined { + async searchById(id: Folder['id']): Promise { + const attributes = this.folders.get(id); + + if (!attributes) return; + + return Folder.from(attributes); + } + + async searchByUuid(id: Folder['uuid']): Promise { + const folders = this.folders.values(); + + for (const attributes of folders) { + if (id === attributes.uuid) { + return Folder.from(attributes); + } + } + + return undefined; + } + + matchingPartial(partial: Partial): Array { const keys = Object.keys(partial) as Array>; - const folder = this.values.find((attributes) => { + const foldersAttributes = this.values.filter((attributes) => { return keys.every( (key: keyof FolderAttributes) => attributes[key] === partial[key] ); }); - if (folder) { - return Folder.from(folder); - } - - return undefined; + return foldersAttributes.map((attributes) => Folder.from(attributes)); } listByPartial(partial: Partial): Promise { diff --git a/tests/context/virtual-drive/files/application/FileCreator.test.ts b/tests/context/virtual-drive/files/application/FileCreator.test.ts index d226d7dbf..c1f015117 100644 --- a/tests/context/virtual-drive/files/application/FileCreator.test.ts +++ b/tests/context/virtual-drive/files/application/FileCreator.test.ts @@ -24,14 +24,14 @@ describe('File Creator', () => { remoteFileSystemMock = new RemoteFileSystemMock(); fileRepository = new FileRepositoryMock(); fileDeleter = FileDeleterFactory.deletionSuccess(); - const folderFinder = FolderFinderFactory.existingFolder(); + const parentFolderFinder = FolderFinderFactory.existingFolder(); eventBus = new EventBusMock(); notifier = new FileSyncNotifierMock(); SUT = new FileCreator( remoteFileSystemMock, fileRepository, - folderFinder, + parentFolderFinder, fileDeleter, eventBus, notifier diff --git a/tests/context/virtual-drive/files/application/FileDeleter.test.ts b/tests/context/virtual-drive/files/application/FileDeleter.test.ts index c49887a93..aa4be0a1c 100644 --- a/tests/context/virtual-drive/files/application/FileDeleter.test.ts +++ b/tests/context/virtual-drive/files/application/FileDeleter.test.ts @@ -43,7 +43,7 @@ describe('File Deleter', () => { repository.searchByContentsIdMock.mockReturnValueOnce(undefined); jest .spyOn(allParentFoldersStatusIsExists, 'run') - .mockReturnValueOnce(false); + .mockResolvedValueOnce(false); await SUT.run(contentsId); @@ -56,7 +56,7 @@ describe('File Deleter', () => { repository.searchByContentsIdMock.mockReturnValueOnce(file); jest .spyOn(allParentFoldersStatusIsExists, 'run') - .mockReturnValueOnce(false); + .mockResolvedValueOnce(false); await SUT.run(file.contentsId); @@ -67,7 +67,9 @@ describe('File Deleter', () => { const file = FileMother.any(); repository.searchByContentsIdMock.mockReturnValueOnce(file); - jest.spyOn(allParentFoldersStatusIsExists, 'run').mockReturnValueOnce(true); + jest + .spyOn(allParentFoldersStatusIsExists, 'run') + .mockResolvedValueOnce(true); await SUT.run(file.contentsId); @@ -78,7 +80,9 @@ describe('File Deleter', () => { const file = FileMother.any(); repository.searchByContentsIdMock.mockReturnValueOnce(file); - jest.spyOn(allParentFoldersStatusIsExists, 'run').mockReturnValueOnce(true); + jest + .spyOn(allParentFoldersStatusIsExists, 'run') + .mockResolvedValueOnce(true); await SUT.run(file.contentsId); diff --git a/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts b/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts index 7b28ece04..02100a829 100644 --- a/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts +++ b/tests/context/virtual-drive/files/application/FilePathUpdater.test.ts @@ -1,7 +1,7 @@ import { FilePathUpdater } from '../../../../../src/context/virtual-drive/files/application/FilePathUpdater'; import { FilePath } from '../../../../../src/context/virtual-drive/files/domain/FilePath'; -import { FolderFinder } from '../../../../../src/context/virtual-drive/folders/application/FolderFinder'; -import { FolderFinderMock } from '../../folders/__mocks__/FolderFinderMock'; +import { ParentFolderFinder } from '../../../../../src/context/virtual-drive/folders/application/ParentFolderFinder'; +import { ParentFolderFinderTestClass } from '../../folders/__test-class__/ParentFolderFinderTestClass'; import { FolderMother } from '../../folders/domain/FolderMother'; import { EventBusMock } from '../../shared/__mock__/EventBusMock'; import { FileRepositoryMock } from '../__mocks__/FileRepositoryMock'; @@ -12,7 +12,7 @@ import { FileMother } from '../domain/FileMother'; describe('File path updater', () => { let repository: FileRepositoryMock; - let folderFinder: FolderFinderMock; + let folderFinder: ParentFolderFinderTestClass; let singleFileMatchingTestClass: SingleFileMatchingTestClass; let localFileSystem: LocalFileSystemMock; let eventBus: EventBusMock; @@ -21,7 +21,7 @@ describe('File path updater', () => { beforeEach(() => { repository = new FileRepositoryMock(); - folderFinder = new FolderFinderMock(); + folderFinder = new ParentFolderFinderTestClass(); singleFileMatchingTestClass = new SingleFileMatchingTestClass(); eventBus = new EventBusMock(); remoteFileSystemMock = new RemoteFileSystemMock(); @@ -32,7 +32,7 @@ describe('File path updater', () => { localFileSystem, repository, singleFileMatchingTestClass, - folderFinder as unknown as FolderFinder, + folderFinder as unknown as ParentFolderFinder, eventBus ); }); diff --git a/tests/context/virtual-drive/folders/__mocks__/FolderFinderFactory.ts b/tests/context/virtual-drive/folders/__mocks__/FolderFinderFactory.ts index d7fd12d79..16de4120c 100644 --- a/tests/context/virtual-drive/folders/__mocks__/FolderFinderFactory.ts +++ b/tests/context/virtual-drive/folders/__mocks__/FolderFinderFactory.ts @@ -1,16 +1,16 @@ -import { FolderFinder } from '../../../../../src/context/virtual-drive/folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../../../../src/context/virtual-drive/folders/application/ParentFolderFinder'; import { Folder } from '../../../../../src/context/virtual-drive/folders/domain/Folder'; import { FolderMother } from '../domain/FolderMother'; import { FolderRepositoryMock } from './FolderRepositoryMock'; export class FolderFinderFactory { - static existingFolder(folder?: Folder): FolderFinder { + static existingFolder(folder?: Folder): ParentFolderFinder { const repository = new FolderRepositoryMock(); const resolved = folder || FolderMother.any(); - repository.searchByPartialMock.mockResolvedValueOnce(resolved); + repository.matchingPartialMock.mockReturnValueOnce([resolved]); - return new FolderFinder(repository); + return new ParentFolderFinder(repository); } } diff --git a/tests/context/virtual-drive/folders/__mocks__/FolderFinderMock.ts b/tests/context/virtual-drive/folders/__mocks__/FolderFinderMock.ts deleted file mode 100644 index e21b4da9b..000000000 --- a/tests/context/virtual-drive/folders/__mocks__/FolderFinderMock.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FolderFinder } from '../../../../../src/context/virtual-drive/folders/application/FolderFinder'; -import { FolderRepository } from '../../../../../src/context/virtual-drive/folders/domain/FolderRepository'; - -export class FolderFinderMock extends FolderFinder { - constructor() { - super({} as FolderRepository); - } - - public mock = jest.fn(); - - run(path: string) { - return this.mock(path); - } -} diff --git a/tests/context/virtual-drive/folders/__mocks__/FolderMoverMock.ts b/tests/context/virtual-drive/folders/__mocks__/FolderMoverMock.ts index b7cb996ab..e719ea1db 100644 --- a/tests/context/virtual-drive/folders/__mocks__/FolderMoverMock.ts +++ b/tests/context/virtual-drive/folders/__mocks__/FolderMoverMock.ts @@ -1,13 +1,17 @@ import { RemoteFileSystem } from '../../../../../src/context/virtual-drive/folders/domain/file-systems/RemoteFileSystem'; -import { FolderFinder } from '../../../../../src/context/virtual-drive/folders/application/FolderFinder'; import { FolderMover } from '../../../../../src/context/virtual-drive/folders/application/FolderMover'; import { FolderRepository } from '../../../../../src/context/virtual-drive/folders/domain/FolderRepository'; import { FolderPath } from '../../../../../src/context/virtual-drive/folders/domain/FolderPath'; import { Folder } from '../../../../../src/context/virtual-drive/folders/domain/Folder'; +import { ParentFolderFinder } from '../../../../../src/context/virtual-drive/folders/application/ParentFolderFinder'; export class FolderMoverMock extends FolderMover { constructor() { - super({} as FolderRepository, {} as RemoteFileSystem, {} as FolderFinder); + super( + {} as FolderRepository, + {} as RemoteFileSystem, + {} as ParentFolderFinder + ); } public readonly mock = jest.fn(); diff --git a/tests/context/virtual-drive/folders/__mocks__/FolderRepositoryMock.ts b/tests/context/virtual-drive/folders/__mocks__/FolderRepositoryMock.ts index c24ed74d1..3f0508b31 100644 --- a/tests/context/virtual-drive/folders/__mocks__/FolderRepositoryMock.ts +++ b/tests/context/virtual-drive/folders/__mocks__/FolderRepositoryMock.ts @@ -6,18 +6,28 @@ import { FolderRepository } from '../../../../../src/context/virtual-drive/folde export class FolderRepositoryMock implements FolderRepository { public readonly allMock = jest.fn(); - public readonly searchByPartialMock = jest.fn(); + public readonly matchingPartialMock = jest.fn(); public readonly listByPartialMock = jest.fn(); public readonly addMock = jest.fn(); public readonly deleteMock = jest.fn(); public readonly updateMock = jest.fn(); + public readonly searchByIdMock = jest.fn(); + public readonly searchByUuidMock = jest.fn(); all(): Promise { return this.allMock(); } - searchByPartial(partial: Partial): Folder | undefined { - return this.searchByPartialMock(partial); + searchById(id: number): Promise { + return this.searchByIdMock(id); + } + + searchByUuid(id: string): Promise { + return this.searchByUuidMock(id); + } + + matchingPartial(partial: Partial): Array { + return this.matchingPartialMock(partial); } listByPartial(partial: Partial): Promise> { diff --git a/tests/context/virtual-drive/folders/__test-class__/ParentFolderFinderTestClass.ts b/tests/context/virtual-drive/folders/__test-class__/ParentFolderFinderTestClass.ts new file mode 100644 index 000000000..7f075c59a --- /dev/null +++ b/tests/context/virtual-drive/folders/__test-class__/ParentFolderFinderTestClass.ts @@ -0,0 +1,15 @@ +import { Path } from '../../../../../src/context/shared/domain/value-objects/Path'; +import { ParentFolderFinder } from '../../../../../src/context/virtual-drive/folders/application/ParentFolderFinder'; +import { FolderRepository } from '../../../../../src/context/virtual-drive/folders/domain/FolderRepository'; + +export class ParentFolderFinderTestClass extends ParentFolderFinder { + constructor() { + super({} as FolderRepository); + } + + public mock = jest.fn(); + + run(path: Path) { + return this.mock(path); + } +} diff --git a/tests/context/virtual-drive/folders/application/FolderDeleter.test.ts b/tests/context/virtual-drive/folders/application/FolderDeleter.test.ts index 451086e4a..a459a12f5 100644 --- a/tests/context/virtual-drive/folders/application/FolderDeleter.test.ts +++ b/tests/context/virtual-drive/folders/application/FolderDeleter.test.ts @@ -1,5 +1,6 @@ import { AllParentFoldersStatusIsExists } from '../../../../../src/context/virtual-drive/folders/application/AllParentFoldersStatusIsExists'; import { FolderDeleter } from '../../../../../src/context/virtual-drive/folders/application/FolderDeleter'; +import { FolderAlreadyTrashed } from '../../../../../src/context/virtual-drive/folders/domain/errors/FolderAlreadyTrashed'; import { FolderLocalFileSystemMock } from '../__mocks__/FolderLocalFileSystemMock'; import { FolderRemoteFileSystemMock } from '../__mocks__/FolderRemoteFileSystemMock'; import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; @@ -31,8 +32,10 @@ describe('Folder deleter', () => { it('trashes an existing folder', async () => { const folder = FolderMother.exists(); - repository.searchByPartialMock.mockReturnValueOnce(folder); - jest.spyOn(allParentFoldersStatusIsExists, 'run').mockReturnValueOnce(true); + repository.searchByUuidMock.mockResolvedValueOnce(folder); + jest + .spyOn(allParentFoldersStatusIsExists, 'run') + .mockResolvedValueOnce(true); await SUT.run(folder.uuid); @@ -42,23 +45,26 @@ describe('Folder deleter', () => { it('throws an error when trashing a folder already trashed', async () => { const folder = FolderMother.trashed(); - repository.searchByPartialMock.mockReturnValueOnce(folder); - jest.spyOn(allParentFoldersStatusIsExists, 'run').mockReturnValueOnce(true); + repository.searchByUuidMock.mockResolvedValueOnce(folder); + jest + .spyOn(allParentFoldersStatusIsExists, 'run') + .mockResolvedValueOnce(true); await SUT.run(folder.uuid).catch((err) => { expect(err).toBeDefined(); + expect(err).toBeInstanceOf(FolderAlreadyTrashed); }); expect(repository.deleteMock).not.toBeCalled(); }); - it('does not delete the folder if a higher folder is already deleted ', async () => { + it('does not delete the folder if a higher folder is already trashed ', async () => { const folder = FolderMother.exists(); - repository.searchByPartialMock.mockReturnValueOnce(folder); + repository.searchByUuidMock.mockResolvedValueOnce(folder); jest .spyOn(allParentFoldersStatusIsExists, 'run') - .mockReturnValueOnce(false); + .mockResolvedValueOnce(false); await SUT.run(folder.uuid).catch((err) => { expect(err).toBeDefined(); @@ -70,8 +76,10 @@ describe('Folder deleter', () => { it('recreates the placeholder if the deletion fails', async () => { const folder = FolderMother.exists(); - repository.searchByPartialMock.mockReturnValueOnce(folder); - jest.spyOn(allParentFoldersStatusIsExists, 'run').mockReturnValueOnce(true); + repository.searchByUuidMock.mockResolvedValueOnce(folder); + jest + .spyOn(allParentFoldersStatusIsExists, 'run') + .mockResolvedValueOnce(true); remote.trashMock.mockRejectedValue(new Error('Error during the deletion')); await SUT.run(folder.uuid); diff --git a/tests/context/virtual-drive/folders/application/FolderMover.test.ts b/tests/context/virtual-drive/folders/application/FolderMover.test.ts index 93ae06223..57f6a973a 100644 --- a/tests/context/virtual-drive/folders/application/FolderMover.test.ts +++ b/tests/context/virtual-drive/folders/application/FolderMover.test.ts @@ -1,4 +1,4 @@ -import { FolderFinder } from '../../../../../src/context/virtual-drive/folders/application/FolderFinder'; +import { ParentFolderFinder } from '../../../../../src/context/virtual-drive/folders/application/ParentFolderFinder'; import { FolderMover } from '../../../../../src/context/virtual-drive/folders/application/FolderMover'; import { FolderPath } from '../../../../../src/context/virtual-drive/folders/domain/FolderPath'; import { FolderRemoteFileSystemMock } from '../__mocks__/FolderRemoteFileSystemMock'; @@ -7,24 +7,27 @@ import { FolderMother } from '../domain/FolderMother'; describe('Folder Mover', () => { let repository: FolderRepositoryMock; - let folderFinder: FolderFinder; + let folderFinder: ParentFolderFinder; let remoteFileSystem: FolderRemoteFileSystemMock; let SUT: FolderMover; beforeEach(() => { repository = new FolderRepositoryMock(); - folderFinder = new FolderFinder(repository); + folderFinder = new ParentFolderFinder(repository); remoteFileSystem = new FolderRemoteFileSystemMock(); SUT = new FolderMover(repository, remoteFileSystem, folderFinder); }); it('Folders cannot be overwrite', async () => { - const folder = FolderMother.in(1, '/folderA/folderB'); + const folder = FolderMother.fromPartial({ + parentId: 1, + path: '/folderA/folderB', + }); const destination = new FolderPath('/folderC/folderB'); - repository.searchByPartialMock.mockImplementation(() => - FolderMother.in(2, destination.value) + repository.matchingPartialMock.mockImplementation(() => + FolderMother.fromPartial({ parentId: 2, path: destination.value }) ); try { @@ -39,13 +42,19 @@ 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 folder = FolderMother.fromPartial({ + parentId: 1, + path: '/folderA/folderB', + }); const destination = new FolderPath('/folderC/folderB'); - const folderC = FolderMother.in(2, '/folderC'); + const folderC = FolderMother.fromPartial({ + parentId: 2, + path: '/folderC', + }); - repository.searchByPartialMock - .mockReturnValueOnce(undefined) - .mockReturnValueOnce(folderC); + repository.matchingPartialMock + .mockReturnValueOnce([]) + .mockReturnValueOnce([folderC]); await SUT.run(folder, destination); diff --git a/tests/context/virtual-drive/folders/application/FolderPathUpdater.test.ts b/tests/context/virtual-drive/folders/application/FolderPathUpdater.test.ts index 3e9489748..212ddcb18 100644 --- a/tests/context/virtual-drive/folders/application/FolderPathUpdater.test.ts +++ b/tests/context/virtual-drive/folders/application/FolderPathUpdater.test.ts @@ -40,7 +40,7 @@ describe('Folder Path Updater', () => { it('throws a Folder Not Found Error if not folder is founded', async () => { const { uuid, destination } = provideInputs(); - repository.searchByPartialMock.mockReturnValueOnce(undefined); + repository.matchingPartialMock.mockReturnValueOnce([]); try { await folderPathUpdater.run(uuid, destination); @@ -53,7 +53,7 @@ describe('Folder Path Updater', () => { const folder = FolderMother.any(); const destination = FolderPathMother.any(); - repository.searchByPartialMock.mockReturnValueOnce(folder); + repository.matchingPartialMock.mockReturnValueOnce([folder]); try { await folderPathUpdater.run(folder.uuid, destination.value); @@ -66,7 +66,7 @@ describe('Folder Path Updater', () => { const folder = FolderMother.any(); const folderPathWithNewName = FolderPathMother.onFolder(folder.dirname); - repository.searchByPartialMock.mockReturnValueOnce(folder); + repository.matchingPartialMock.mockReturnValueOnce([folder]); await folderPathUpdater.run(folder.uuid, folderPathWithNewName.value); @@ -84,7 +84,7 @@ describe('Folder Path Updater', () => { folder.name + '(1)' ); - repository.searchByPartialMock.mockReturnValueOnce(folder); + repository.matchingPartialMock.mockReturnValueOnce([folder]); await folderPathUpdater.run(folder.uuid, desiredPath.value); @@ -98,7 +98,7 @@ describe('Folder Path Updater', () => { it('throws a Path Has Not Changed Error if the path desired path is the same as the current', async () => { const folder = FolderMother.any(); - repository.searchByPartialMock.mockReturnValueOnce(folder); + repository.matchingPartialMock.mockReturnValueOnce([folder]); try { await folderPathUpdater.run(folder.uuid, folder.path); diff --git a/tests/context/virtual-drive/folders/application/SynchronizeOfflineModifications.test.ts b/tests/context/virtual-drive/folders/application/SynchronizeOfflineModifications.test.ts index aa244ba35..04d1cb7ae 100644 --- a/tests/context/virtual-drive/folders/application/SynchronizeOfflineModifications.test.ts +++ b/tests/context/virtual-drive/folders/application/SynchronizeOfflineModifications.test.ts @@ -51,7 +51,7 @@ describe('Synchronize Offline Modifications', () => { await SUT.run(FolderUuid.random().value); - expect(repository.searchByPartialMock).not.toBeCalled(); + expect(repository.matchingPartialMock).not.toBeCalled(); }); it('throws an error if there is no folder with the given uuid', async () => { @@ -59,7 +59,7 @@ describe('Synchronize Offline Modifications', () => { .spyOn(offlineRepository, 'searchByPartial') .mockReturnValueOnce(OfflineFolderMother.random()); - repository.searchByPartialMock.mockReturnValueOnce(undefined); + repository.matchingPartialMock.mockReturnValueOnce(undefined); try { await SUT.run(FolderUuid.random().value); @@ -85,7 +85,7 @@ describe('Synchronize Offline Modifications', () => { .spyOn(offlineRepository, 'searchByPartial') .mockReturnValueOnce(offlineFolder); - repository.searchByPartialMock.mockReturnValueOnce(folder); + repository.matchingPartialMock.mockReturnValueOnce(folder); const renamerSpy = jest.spyOn(renamer, 'run'); @@ -112,7 +112,7 @@ describe('Synchronize Offline Modifications', () => { const renamerSpy = jest.spyOn(renamer, 'run'); - repository.searchByPartialMock.mockReturnValueOnce(folder); + repository.matchingPartialMock.mockReturnValueOnce([folder]); const event = new FolderRenamedDomainEvent({ aggregateId: offlineFolder.uuid, @@ -155,9 +155,9 @@ describe('Synchronize Offline Modifications', () => { const renamerSpy = jest.spyOn(renamer, 'run'); - repository.searchByPartialMock - .mockReturnValueOnce(afterCreation) - .mockReturnValueOnce(afterFirstRename); + repository.matchingPartialMock + .mockReturnValueOnce([afterCreation]) + .mockReturnValueOnce([afterFirstRename]); const uuid = offlineFolder.uuid; diff --git a/tests/context/virtual-drive/folders/domain/FolderMother.ts b/tests/context/virtual-drive/folders/domain/FolderMother.ts index c34d93704..8c67cf830 100644 --- a/tests/context/virtual-drive/folders/domain/FolderMother.ts +++ b/tests/context/virtual-drive/folders/domain/FolderMother.ts @@ -34,18 +34,6 @@ export class FolderMother { }); } - static in(folderId: number, path: string) { - return Folder.from({ - id: 20445, - uuid: FolderUuid.random().value, - path, - parentId: folderId, - updatedAt: new Date().toISOString(), - createdAt: new Date().toISOString(), - status: FolderStatuses.EXISTS, - }); - } - static withId(folderId: number) { return Folder.from({ id: folderId, From ad4ff6d65eb03c56405d693bc2d97c2187d63e9f Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 27 Feb 2024 15:35:01 +0100 Subject: [PATCH 13/15] test: folder creator --- .../fuse/callbacks/GetAttributesCallback.ts | 7 +- src/apps/fuse/callbacks/RenameOrMoveFolder.ts | 6 +- .../virtual-drive/folders/FoldersContainer.ts | 6 +- .../virtual-drive/folders/builder.ts | 8 +- .../application/SingleFileMatchingFinder.ts | 16 +- .../application/SingleFileMatchingSearcher.ts | 12 +- .../domain/errors/OnlyOneFileExpectedError.ts | 5 + .../folders/application/FolderCreator.ts | 18 +-- .../application/SingleFolderMatchingFinder.ts | 14 +- .../SingleFolderMatchingSearcher.ts | 22 +++ .../domain/errors/FolderAlreadyExists.ts | 7 - .../errors/FolderInPathAlreadyExistsError.ts | 7 + .../errors/OnlyOneFolderExpectedError.ts | 5 + .../folders/application/FolderCreator.test.ts | 152 ++++++++++++++++++ 14 files changed, 247 insertions(+), 38 deletions(-) create mode 100644 src/context/virtual-drive/files/domain/errors/OnlyOneFileExpectedError.ts create mode 100644 src/context/virtual-drive/folders/application/SingleFolderMatchingSearcher.ts delete mode 100644 src/context/virtual-drive/folders/domain/errors/FolderAlreadyExists.ts create mode 100644 src/context/virtual-drive/folders/domain/errors/FolderInPathAlreadyExistsError.ts create mode 100644 src/context/virtual-drive/folders/domain/errors/OnlyOneFolderExpectedError.ts create mode 100644 tests/context/virtual-drive/folders/application/FolderCreator.test.ts diff --git a/src/apps/fuse/callbacks/GetAttributesCallback.ts b/src/apps/fuse/callbacks/GetAttributesCallback.ts index a254f5c2d..6d4f6bf1e 100644 --- a/src/apps/fuse/callbacks/GetAttributesCallback.ts +++ b/src/apps/fuse/callbacks/GetAttributesCallback.ts @@ -60,9 +60,10 @@ export class GetAttributesCallback extends FuseCallback> { - const folder = await this.container.folderSearcher.run({ path: src }); + const folder = await this.container.singleFolderMatchingSearcher.run({ + path: src, + status: FolderStatuses.EXISTS, + }); if (!folder) { return right(RenameOrMoveFolder.NO_OP); diff --git a/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts b/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts index 1ac6db7e3..9a2f848a9 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/folders/FoldersContainer.ts @@ -2,12 +2,13 @@ import { AllParentFoldersStatusIsExists } from '../../../../../context/virtual-d import { FolderCreator } from '../../../../../context/virtual-drive/folders/application/FolderCreator'; import { FolderCreatorFromOfflineFolder } from '../../../../../context/virtual-drive/folders/application/FolderCreatorFromOfflineFolder'; import { FolderDeleter } from '../../../../../context/virtual-drive/folders/application/FolderDeleter'; -import { ParentFolderFinder } from '../../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { FolderPathUpdater } from '../../../../../context/virtual-drive/folders/application/FolderPathUpdater'; import { FolderRepositoryInitializer } from '../../../../../context/virtual-drive/folders/application/FolderRepositoryInitializer'; import { FoldersByParentPathLister } from '../../../../../context/virtual-drive/folders/application/FoldersByParentPathLister'; -import { SyncFolderMessenger } from '../../../../../context/virtual-drive/folders/domain/SyncFolderMessenger'; +import { ParentFolderFinder } from '../../../../../context/virtual-drive/folders/application/ParentFolderFinder'; import { SingleFolderMatchingFinder } from '../../../../../context/virtual-drive/folders/application/SingleFolderMatchingFinder'; +import { SingleFolderMatchingSearcher } from '../../../../../context/virtual-drive/folders/application/SingleFolderMatchingSearcher'; +import { SyncFolderMessenger } from '../../../../../context/virtual-drive/folders/domain/SyncFolderMessenger'; export interface FoldersContainer { parentFolderFinder: ParentFolderFinder; @@ -20,4 +21,5 @@ export interface FoldersContainer { folderRepositoryInitiator: FolderRepositoryInitializer; syncFolderMessenger: SyncFolderMessenger; singleFolderMatchingFinder: SingleFolderMatchingFinder; + singleFolderMatchingSearcher: SingleFolderMatchingSearcher; } diff --git a/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts b/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts index d0df5776d..4488d9625 100644 --- a/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts +++ b/src/apps/fuse/dependency-injection/virtual-drive/folders/builder.ts @@ -18,6 +18,7 @@ import { DependencyInjectionEventBus } from '../../common/eventBus'; import { FoldersContainer } from './FoldersContainer'; import { SingleFolderMatchingFinder } from '../../../../../context/virtual-drive/folders/application/SingleFolderMatchingFinder'; +import { SingleFolderMatchingSearcher } from '../../../../../context/virtual-drive/folders/application/SingleFolderMatchingSearcher'; export async function buildFoldersContainer( initialFolders: Array @@ -43,6 +44,9 @@ export async function buildFoldersContainer( const parentFolderFinder = new ParentFolderFinder(repository); const singleFolderMatchingFinder = new SingleFolderMatchingFinder(repository); + const singleFolderMatchingSearcher = new SingleFolderMatchingSearcher( + repository + ); const foldersByParentPathSearcher = new FoldersByParentPathLister( parentFolderFinder, @@ -81,7 +85,7 @@ export async function buildFoldersContainer( const folderCreator = new FolderCreator( repository, - singleFolderMatchingFinder, + parentFolderFinder, remoteFileSystem, eventBus ); @@ -103,5 +107,7 @@ export async function buildFoldersContainer( folderDeleter, syncFolderMessenger, folderRepositoryInitiator, + singleFolderMatchingFinder, + singleFolderMatchingSearcher, }; } diff --git a/src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts b/src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts index 15e5fbb3c..bd6ff2955 100644 --- a/src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts +++ b/src/context/virtual-drive/files/application/SingleFileMatchingFinder.ts @@ -1,20 +1,24 @@ import { File, FileAttributes } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; import { FileNotFoundError } from '../domain/errors/FileNotFoundError'; +import { OnlyOneFileExpectedError } from '../domain/errors/OnlyOneFileExpectedError'; export class SingleFileMatchingFinder { constructor(private readonly repository: FileRepository) {} + /** + * @param partial a partial object of the attributes of the file in search + * @returns the matching file for the given partial attributes or undefined if no one matches + * @throws an {@link OnlyOneFileExpectedError} when it finds more than one file + * or a {@link FileNotFoundError} when no file is founded + */ async run(partial: Partial): Promise { const files = this.repository.matchingPartial(partial); - if (!files) { - throw new FileNotFoundError('unknown'); - } + if (files.length === 0) + throw new FileNotFoundError(JSON.stringify(partial)); - if (files.length > 1) { - throw new Error('Expected to find a singular file'); - } + if (files.length > 1) throw new OnlyOneFileExpectedError(); return files[0]; } diff --git a/src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts b/src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts index 357b7ec3e..8cb0ece10 100644 --- a/src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts +++ b/src/context/virtual-drive/files/application/SingleFileMatchingSearcher.ts @@ -1,17 +1,21 @@ import { File, FileAttributes } from '../domain/File'; import { FileRepository } from '../domain/FileRepository'; +import { OnlyOneFileExpectedError } from '../domain/errors/OnlyOneFileExpectedError'; export class SingleFileMatchingSearcher { constructor(private readonly repository: FileRepository) {} + /** + * @param partial a partial object of the attributes of the file in search + * @returns the matching file for the given partial attributes or undefined if no one matches + * @throws an {@link OnlyOneFileExpectedError} when it finds more than one file + */ async run(attributes: Partial): Promise { const files = this.repository.matchingPartial(attributes); - if (!files) return; + if (files.length > 1) throw new OnlyOneFileExpectedError(); - if (files.length > 1) { - throw new Error('Expected to find a singular file'); - } + if (files.length === 0) return; return files[0]; } diff --git a/src/context/virtual-drive/files/domain/errors/OnlyOneFileExpectedError.ts b/src/context/virtual-drive/files/domain/errors/OnlyOneFileExpectedError.ts new file mode 100644 index 000000000..200325333 --- /dev/null +++ b/src/context/virtual-drive/files/domain/errors/OnlyOneFileExpectedError.ts @@ -0,0 +1,5 @@ +export class OnlyOneFileExpectedError extends Error { + constructor() { + super('Expected to find only one file'); + } +} diff --git a/src/context/virtual-drive/folders/application/FolderCreator.ts b/src/context/virtual-drive/folders/application/FolderCreator.ts index 5b19530e1..04757fa87 100644 --- a/src/context/virtual-drive/folders/application/FolderCreator.ts +++ b/src/context/virtual-drive/folders/application/FolderCreator.ts @@ -7,38 +7,38 @@ import { FolderRepository } from '../domain/FolderRepository'; import { FolderStatuses } from '../domain/FolderStatus'; import { FolderUpdatedAt } from '../domain/FolderUpdatedAt'; import { FolderUuid } from '../domain/FolderUuid'; -import { FolderAlreadyExists } from '../domain/errors/FolderAlreadyExists'; +import { FolderInPathAlreadyExistsError } from '../domain/errors/FolderInPathAlreadyExistsError'; import { RemoteFileSystem } from '../domain/file-systems/RemoteFileSystem'; -import { SingleFolderMatchingFinder } from './SingleFolderMatchingFinder'; +import { ParentFolderFinder } from './ParentFolderFinder'; export class FolderCreator { constructor( private readonly repository: FolderRepository, - private readonly singleFolderFinder: SingleFolderMatchingFinder, + private readonly parentFolderFinder: ParentFolderFinder, private readonly remote: RemoteFileSystem, private readonly eventBus: EventBus ) {} - private ensureItDoesNotExists(path: FolderPath): void { - const folder = this.repository.matchingPartial({ + private async ensureItDoesNotExists(path: FolderPath): Promise { + const result = this.repository.matchingPartial({ path: path.value, status: FolderStatuses.EXISTS, }); - if (folder.length > 0) { - throw new FolderAlreadyExists(folder[0]); + if (result.length > 0) { + throw new FolderInPathAlreadyExistsError(path); } } private async findParentId(path: FolderPath): Promise { - const parent = await this.singleFolderFinder.run({ path: path.value }); + const parent = await this.parentFolderFinder.run(path); return new FolderId(parent.id); } async run(path: string): Promise { const folderPath = new FolderPath(path); - this.ensureItDoesNotExists(folderPath); + await this.ensureItDoesNotExists(folderPath); const parentId = await this.findParentId(folderPath); diff --git a/src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts b/src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts index b543f48cb..75d8e7aaa 100644 --- a/src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts +++ b/src/context/virtual-drive/folders/application/SingleFolderMatchingFinder.ts @@ -1,20 +1,24 @@ import { Folder, FolderAttributes } from '../domain/Folder'; import { FolderRepository } from '../domain/FolderRepository'; import { FolderNotFoundError } from '../domain/errors/FolderNotFoundError'; +import { OnlyOneFolderExpectedError } from '../domain/errors/OnlyOneFolderExpectedError'; export class SingleFolderMatchingFinder { constructor(private readonly repository: FolderRepository) {} + /** + * @param partial a partial object of the attributes of the folder in search + * @returns the matching folder for the given partial attributes + * @throws an {@link OnlyOneFolderExpectedError} if it finds more than one folder + * or a {@link FolderNotFoundError} if no folder is founded + */ async run(partial: Partial): Promise { const folders = this.repository.matchingPartial(partial); - if (folders.length === 0) { + if (folders.length === 0) throw new FolderNotFoundError(JSON.stringify(partial)); - } - if (folders.length > 1) { - throw new Error('Expected to find a singular folder'); - } + if (folders.length > 1) throw new OnlyOneFolderExpectedError(); return folders[0]; } diff --git a/src/context/virtual-drive/folders/application/SingleFolderMatchingSearcher.ts b/src/context/virtual-drive/folders/application/SingleFolderMatchingSearcher.ts new file mode 100644 index 000000000..a459c6de9 --- /dev/null +++ b/src/context/virtual-drive/folders/application/SingleFolderMatchingSearcher.ts @@ -0,0 +1,22 @@ +import { Folder, FolderAttributes } from '../domain/Folder'; +import { FolderRepository } from '../domain/FolderRepository'; +import { OnlyOneFolderExpectedError } from '../domain/errors/OnlyOneFolderExpectedError'; + +export class SingleFolderMatchingSearcher { + constructor(private readonly repository: FolderRepository) {} + + /** + * @param partial a partial object of the attributes of the folder in search + * @returns the matching folder for the given partial attributes or undefined if no one matches + * @throws an {@link OnlyOneFolderExpectedError} when it finds more than one folder + */ + async run(partial: Partial): Promise { + const folders = this.repository.matchingPartial(partial); + + if (folders.length > 1) throw new OnlyOneFolderExpectedError(); + + if (folders.length === 0) return; + + return folders[0]; + } +} diff --git a/src/context/virtual-drive/folders/domain/errors/FolderAlreadyExists.ts b/src/context/virtual-drive/folders/domain/errors/FolderAlreadyExists.ts deleted file mode 100644 index 976d6d879..000000000 --- a/src/context/virtual-drive/folders/domain/errors/FolderAlreadyExists.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Folder } from '../Folder'; - -export class FolderAlreadyExists extends Error { - constructor(folder: Folder) { - super(`Folder ${folder.path} already exists with id: ${folder.uuid}`); - } -} diff --git a/src/context/virtual-drive/folders/domain/errors/FolderInPathAlreadyExistsError.ts b/src/context/virtual-drive/folders/domain/errors/FolderInPathAlreadyExistsError.ts new file mode 100644 index 000000000..f482bcb8b --- /dev/null +++ b/src/context/virtual-drive/folders/domain/errors/FolderInPathAlreadyExistsError.ts @@ -0,0 +1,7 @@ +import { FolderPath } from '../FolderPath'; + +export class FolderInPathAlreadyExistsError extends Error { + constructor(path: FolderPath) { + super(`Folder in ${path.value} already exists`); + } +} diff --git a/src/context/virtual-drive/folders/domain/errors/OnlyOneFolderExpectedError.ts b/src/context/virtual-drive/folders/domain/errors/OnlyOneFolderExpectedError.ts new file mode 100644 index 000000000..7dd94f8d2 --- /dev/null +++ b/src/context/virtual-drive/folders/domain/errors/OnlyOneFolderExpectedError.ts @@ -0,0 +1,5 @@ +export class OnlyOneFolderExpectedError extends Error { + constructor() { + super('Expected to find only one folder'); + } +} diff --git a/tests/context/virtual-drive/folders/application/FolderCreator.test.ts b/tests/context/virtual-drive/folders/application/FolderCreator.test.ts new file mode 100644 index 000000000..c64d92679 --- /dev/null +++ b/tests/context/virtual-drive/folders/application/FolderCreator.test.ts @@ -0,0 +1,152 @@ +import { InvalidArgumentError } from '../../../../../src/context/shared/domain/errors/InvalidArgumentError'; +import { FolderCreator } from '../../../../../src/context/virtual-drive/folders/application/FolderCreator'; +import { ParentFolderFinder } from '../../../../../src/context/virtual-drive/folders/application/ParentFolderFinder'; +import { Folder } from '../../../../../src/context/virtual-drive/folders/domain/Folder'; +import { FolderId } from '../../../../../src/context/virtual-drive/folders/domain/FolderId'; +import { FolderStatuses } from '../../../../../src/context/virtual-drive/folders/domain/FolderStatus'; +import { FolderInPathAlreadyExistsError } from '../../../../../src/context/virtual-drive/folders/domain/errors/FolderInPathAlreadyExistsError'; +import { FolderNotFoundError } from '../../../../../src/context/virtual-drive/folders/domain/errors/FolderNotFoundError'; +import { FolderPersistedDto } from '../../../../../src/context/virtual-drive/folders/domain/file-systems/RemoteFileSystem'; +import { EventBusMock } from '../../shared/__mock__/EventBusMock'; +import { FolderRemoteFileSystemMock } from '../__mocks__/FolderRemoteFileSystemMock'; +import { FolderRepositoryMock } from '../__mocks__/FolderRepositoryMock'; +import { FolderMother } from '../domain/FolderMother'; +import { FolderPathMother } from '../domain/FolderPathMother'; + +describe('Folder Creator', () => { + let repository: FolderRepositoryMock; + let remote: FolderRemoteFileSystemMock; + let eventBus: EventBusMock; + + let SUT: FolderCreator; + + beforeEach(() => { + repository = new FolderRepositoryMock(); + remote = new FolderRemoteFileSystemMock(); + eventBus = new EventBusMock(); + + const parentFolderFinder = new ParentFolderFinder(repository); + + SUT = new FolderCreator(repository, parentFolderFinder, remote, eventBus); + }); + + const mockCorrectPersistance = (folder: Folder) => { + remote.persistMock.mockResolvedValueOnce({ + id: folder.id, + uuid: folder.uuid, + createdAt: folder.createdAt.toISOString(), + updatedAt: folder.updatedAt.toISOString(), + parentId: folder.parentId as number, + } satisfies FolderPersistedDto); + }; + + it('throws an InvalidArgument error if the path is not a valid posix path', async () => { + const nonPosixPath = 'C:\\Users\\Internxt'; + + try { + await SUT.run(nonPosixPath); + fail('Expected InvalidArgumentError, but no error was thrown.'); + } catch (err) { + expect(err).toBeInstanceOf(InvalidArgumentError); + } + }); + + it('throws a FolderInPathAlreadyExists error if there is a folder on the desired path', async () => { + const path = FolderPathMother.any().value; + const folder = FolderMother.fromPartial({ + path, + status: FolderStatuses.EXISTS, + }); + + repository.matchingPartialMock.mockReturnValueOnce([folder]); + + try { + await SUT.run(path); + fail('Expected FolderInPathAlreadyExistsError, but no error was thrown.'); + } catch (err) { + expect(err).toBeInstanceOf(FolderInPathAlreadyExistsError); + } + }); + + it('throws a FolderNotFounded error if the parent folder is not founded', async () => { + const path = FolderPathMother.any().value; + + repository.matchingPartialMock + .mockReturnValueOnce([]) + .mockReturnValueOnce([]); + + try { + await SUT.run(path); + fail('Expected FolderNotFoundError, but no error was thrown.'); + } catch (err) { + expect(err).toBeInstanceOf(FolderNotFoundError); + } + }); + + it('persists a folder in the remote fs when the parent folder is found and the path is available', async () => { + const path = FolderPathMother.any(); + const parent = FolderMother.fromPartial({ path: path.dirname() }); + const folderCreated = FolderMother.fromPartial({ + path: path.value, + parentId: parent.id, + }); + + mockCorrectPersistance(folderCreated); + + repository.matchingPartialMock + .mockReturnValueOnce([]) + .mockReturnValueOnce([parent]); + + await SUT.run(path.value); + + expect(remote.persistMock).toBeCalledWith( + path, + new FolderId(parent.id), + undefined // optional parameter + ); + }); + + it('add the folder to the repository', async () => { + const path = FolderPathMother.any(); + const parent = FolderMother.fromPartial({ path: path.dirname() }); + const folderCreated = FolderMother.fromPartial({ + path: path.value, + parentId: parent.id, + }); + + mockCorrectPersistance(folderCreated); + + repository.matchingPartialMock + .mockReturnValueOnce([]) + .mockReturnValueOnce([parent]); + + await SUT.run(path.value); + + expect(repository.addMock).toBeCalledWith( + expect.objectContaining(folderCreated) + ); + }); + + it('publishes folder created event', async () => { + const path = FolderPathMother.any(); + const parent = FolderMother.fromPartial({ path: path.dirname() }); + const folderCreated = FolderMother.fromPartial({ + path: path.value, + parentId: parent.id, + }); + + mockCorrectPersistance(folderCreated); + + repository.matchingPartialMock + .mockReturnValueOnce([]) + .mockReturnValueOnce([parent]); + + await SUT.run(path.value); + + expect(eventBus.publishMock).toBeCalledWith( + expect.arrayContaining([ + expect.objectContaining({ aggregateId: folderCreated.uuid }), + ]) + ); + }); +}); From db1bdb20d2c56bcf2c2cb72e0c3183a830061e79 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 27 Feb 2024 15:38:07 +0100 Subject: [PATCH 14/15] chore: remove list by partial in folder repository --- .../application/FoldersByParentPathLister.ts | 2 +- .../folders/domain/FolderRepository.ts | 3 +-- .../infrastructure/InMemoryFolderRepository.ts | 16 ---------------- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts b/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts index 9de21c596..737cb3423 100644 --- a/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts +++ b/src/context/virtual-drive/folders/application/FoldersByParentPathLister.ts @@ -12,7 +12,7 @@ export class FoldersByParentPathLister { async run(folderPath: FolderPath): Promise> { const parent = await this.parentFolderFinder.run(folderPath); - const folders = await this.repository.listByPartial({ + const folders = this.repository.matchingPartial({ parentId: parent.id, }); diff --git a/src/context/virtual-drive/folders/domain/FolderRepository.ts b/src/context/virtual-drive/folders/domain/FolderRepository.ts index cafd8cfed..eb8e701cc 100644 --- a/src/context/virtual-drive/folders/domain/FolderRepository.ts +++ b/src/context/virtual-drive/folders/domain/FolderRepository.ts @@ -4,12 +4,11 @@ export interface FolderRepository { all(): Promise>; searchById(id: Folder['id']): Promise; + searchByUuid(id: Folder['uuid']): Promise; matchingPartial(partial: Partial): Array; - listByPartial(partial: Partial): Promise>; - add(folder: Folder): Promise; delete(id: Folder['id']): Promise; diff --git a/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts b/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts index 325ee16be..4bde24569 100644 --- a/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts +++ b/src/context/virtual-drive/folders/infrastructure/InMemoryFolderRepository.ts @@ -52,22 +52,6 @@ export class InMemoryFolderRepository implements FolderRepository { return foldersAttributes.map((attributes) => Folder.from(attributes)); } - listByPartial(partial: Partial): Promise { - const keys = Object.keys(partial) as Array>; - - const folderAttributes = this.values.filter((attributes) => { - return keys.every( - (key: keyof FolderAttributes) => attributes[key] === partial[key] - ); - }); - - const folders = folderAttributes.map((attributes) => - Folder.from(attributes) - ); - - return Promise.resolve(folders); - } - async add(folder: Folder): Promise { this.folders.set(folder.id, folder.attributes()); } From 98d0fc9f33e585c1550c8a7ef3de9c2d71759c12 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Tue, 27 Feb 2024 15:44:02 +0100 Subject: [PATCH 15/15] fix: sonar issues --- .../callbacks-controllers/controllers/AddController.ts | 2 +- src/context/virtual-drive/files/application/FileDeleter.ts | 2 +- src/context/virtual-drive/folders/application/FolderDeleter.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts b/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts index 48c766477..ac81949c1 100644 --- a/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts +++ b/src/apps/sync-engine/callbacks-controllers/controllers/AddController.ts @@ -113,7 +113,7 @@ export class AddController extends CallbackController { posixRelativePath: string ): Promise { try { - return this.offlineFolderCreator.run(posixRelativePath); + return await this.offlineFolderCreator.run(posixRelativePath); } catch (error) { if (error instanceof FolderNotFoundError) { // father created diff --git a/src/context/virtual-drive/files/application/FileDeleter.ts b/src/context/virtual-drive/files/application/FileDeleter.ts index 83a3d541b..5f4180738 100644 --- a/src/context/virtual-drive/files/application/FileDeleter.ts +++ b/src/context/virtual-drive/files/application/FileDeleter.ts @@ -29,7 +29,7 @@ export class FileDeleter { return; } - const allParentsExists = this.allParentFoldersStatusIsExists.run( + const allParentsExists = await this.allParentFoldersStatusIsExists.run( file.folderId ); diff --git a/src/context/virtual-drive/folders/application/FolderDeleter.ts b/src/context/virtual-drive/folders/application/FolderDeleter.ts index b4d0dffef..2c3fe5cc4 100644 --- a/src/context/virtual-drive/folders/application/FolderDeleter.ts +++ b/src/context/virtual-drive/folders/application/FolderDeleter.ts @@ -27,7 +27,7 @@ export class FolderDeleter { throw new ActionNotPermittedError('Trash root folder'); } - const allParentsExists = this.allParentFoldersStatusIsExists.run( + const allParentsExists = await this.allParentFoldersStatusIsExists.run( // TODO: Create a new aggregate root for root folder so the rest have the parent Id as number folder.parentId as number );