From 5e1dbe63acd4f14b8979f5000da76acf35c607b8 Mon Sep 17 00:00:00 2001 From: joan vicens Date: Mon, 29 Jan 2024 17:34:42 +0100 Subject: [PATCH] chore: simplify upload use case --- .../offlineContentsContainerBuilder.ts | 15 ++-- .../OfflineContentsPathCalculator.ts | 10 --- .../application/OfflineContentsUploader.ts | 89 ++++--------------- .../contents/domain/OfflineContents.ts | 32 ++----- .../domain/OfflineContentsManagersFactory.ts | 22 ++--- ...neContentsId.ts => OfflineContentsName.ts} | 2 +- .../domain/OfflineContentsRepository.ts | 10 ++- .../EnvironmentOfflineContentsUploader.ts | 9 +- ...onmentRemoteFileContentsManagersFactory.ts | 53 +++++++++-- .../NodeFSOfflineContentsRepository.ts | 28 ++++-- 10 files changed, 122 insertions(+), 148 deletions(-) delete mode 100644 src/context/offline-drive/contents/application/OfflineContentsPathCalculator.ts rename src/context/offline-drive/contents/domain/{OfflineContentsId.ts => OfflineContentsName.ts} (57%) diff --git a/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts b/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts index eee146bcc..9d7513f19 100644 --- a/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts +++ b/src/apps/fuse/dependency-injection/offline/OfflineContents/offlineContentsContainerBuilder.ts @@ -1,6 +1,5 @@ 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'; @@ -36,18 +35,16 @@ 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( - offlineContentsPathCalculator, - environmentOfflineContentsManagersFactory, repository, - tracker, + environmentOfflineContentsManagersFactory, eventBus ); 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 b52ec3b88..000000000 --- a/src/context/offline-drive/contents/application/OfflineContentsPathCalculator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OfflineFileId } from '../../files/domain/OfflineFileId'; -import { OfflineContentsRepository } from '../domain/OfflineContentsRepository'; - -export class OfflineContentsPathCalculator { - constructor(private readonly repository: OfflineContentsRepository) {} - - async run(id: OfflineFileId): 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 39db2c7da..f87426f68 100644 --- a/src/context/offline-drive/contents/application/OfflineContentsUploader.ts +++ b/src/context/offline-drive/contents/application/OfflineContentsUploader.ts @@ -1,91 +1,36 @@ -import { UploadProgressTracker } from '../../../shared/domain/UploadProgressTracker'; import { EventBus } from '../../../virtual-drive/shared/domain/EventBus'; -import { - OfflineContentUploader, - OfflineContentsManagersFactory, -} from '../domain/OfflineContentsManagersFactory'; +import { OfflineContentsManagersFactory } from '../domain/OfflineContentsManagersFactory'; import { OfflineContentsRepository } from '../domain/OfflineContentsRepository'; -import Logger from 'electron-log'; -import { OfflineContentsPathCalculator } from './OfflineContentsPathCalculator'; import { OfflineContentsUploadedDomainEvent } from '../domain/events/OfflineContentsUploadedDomainEvent'; import { FilePath } from '../../../virtual-drive/files/domain/FilePath'; -import { OfflineFileId } from '../../files/domain/OfflineFileId'; +import { OfflineContentsName } from '../domain/OfflineContentsName'; export class OfflineContentsUploader { constructor( - private readonly offlineContentsPathCalculator: OfflineContentsPathCalculator, - private readonly contentsManagersFactory: OfflineContentsManagersFactory, private readonly repository: OfflineContentsRepository, - private readonly progressTracker: UploadProgressTracker, + private readonly contentsManagersFactory: OfflineContentsManagersFactory, private readonly eventBus: EventBus ) {} - private registerEvents( - path: FilePath, - size: number, - uploader: OfflineContentUploader - ) { - uploader.on('start', () => { - this.progressTracker.uploadStarted(path.name(), path.extension(), size, { - elapsedTime: uploader.elapsedTime(), - }); - Logger.debug('FILE_UPLOADING', { - processInfo: { elapsedTime: uploader.elapsedTime() }, - }); - }); - - uploader.on('progress', (progress: number) => { - this.progressTracker.uploadProgress(path.name(), path.extension(), size, { - elapsedTime: uploader.elapsedTime(), - progress, - }); - Logger.debug('FILE_UPLOADING', { - processInfo: { elapsedTime: uploader.elapsedTime(), progress }, - }); - }); - - uploader.on('error', (error: Error) => { - this.progressTracker.uploadError( - path.name(), - path.extension(), - error.message - ); - Logger.debug('FILE_UPLOAD_ERROR', { - error: error.message, - }); - }); - - uploader.on('finish', () => { - this.progressTracker.uploadCompleted( - path.name(), - path.extension(), - size, - { - elapsedTime: uploader.elapsedTime(), - } - ); - }); - } - - async run(name: OfflineFileId, path: FilePath): Promise { - const absolutePath = await this.offlineContentsPathCalculator.run(name); - - const { - contents: readable, - abortSignal, - size, - } = await this.repository.provide(absolutePath); - - const uploader = this.contentsManagersFactory.uploader(size, abortSignal); + async run(name: OfflineContentsName, path: FilePath): Promise { + const { contents, stream, abortSignal } = await this.repository.read(name); - this.registerEvents(path, size, uploader); + const uploader = this.contentsManagersFactory.uploader( + stream, + contents, + { + name: path.name(), + extension: path.extension(), + }, + abortSignal + ); - const contentsId = await uploader.upload(readable, size); + const contentsId = await uploader(); const contentsUploadedEvent = new OfflineContentsUploadedDomainEvent({ aggregateId: contentsId, - offlineContentsPath: absolutePath, - size, + offlineContentsPath: contents.absolutePath, + size: contents.size, path: path.value, }); 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 39c85e4ab..845dd9bcf 100644 --- a/src/context/offline-drive/contents/domain/OfflineContentsRepository.ts +++ b/src/context/offline-drive/contents/domain/OfflineContentsRepository.ts @@ -1,16 +1,18 @@ import { Readable } from 'stream'; import { OfflineFile } from '../../files/domain/OfflineFile'; +import { OfflineContents } from './OfflineContents'; +import { OfflineContentsName } from './OfflineContentsName'; export interface OfflineContentsRepository { writeToFile(id: OfflineFile['id'], buffer: Buffer): Promise; createEmptyFile(id: OfflineFile['id']): Promise; - getAbsolutePath(id: OfflineFile['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/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 058fb6e2c..fd0cf8537 100644 --- a/src/context/offline-drive/contents/infrastructure/NodeFSOfflineContentsRepository.ts +++ b/src/context/offline-drive/contents/infrastructure/NodeFSOfflineContentsRepository.ts @@ -6,6 +6,8 @@ import { LocalFileContentsDirectoryProvider } from '../../../virtual-drive/share 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: OfflineFile['id']): Promise { + private async filePath(name: OfflineContentsName): Promise { const folder = await this.folderPath(); - return path.join(folder, id.value); + return path.join(folder, name.value); } private createAbortableStream(filePath: string): { @@ -69,11 +71,17 @@ export class NodeFSOfflineContentsRepository 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); @@ -94,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, }; }