diff --git a/package.json b/package.json index 75f7653f6..a8827645d 100644 --- a/package.json +++ b/package.json @@ -264,7 +264,7 @@ "@iconscout/react-unicons": "^1.1.6", "@internxt/inxt-js": "^2.0.8", "@internxt/lib": "^1.1.6", - "@internxt/sdk": "^1.4.33", + "@internxt/sdk": "^1.4.68", "@phosphor-icons/react": "2.0.9", "@radix-ui/react-select": "^1.2.2", "@sentry/electron": "^4.5.0", diff --git a/src/apps/sync-engine/BindingManager.ts b/src/apps/sync-engine/BindingManager.ts index 0c112941e..220a0083e 100644 --- a/src/apps/sync-engine/BindingManager.ts +++ b/src/apps/sync-engine/BindingManager.ts @@ -284,10 +284,15 @@ export class BindingsManager { } private async polling(): Promise { - Logger.info('[SYNC ENGINE] Monitoring polling...'); - const fileInPendingPaths = - (await this.container.virtualDrive.getPlaceholderWithStatePending()) as Array; - Logger.info('[SYNC ENGINE] fileInPendingPaths', fileInPendingPaths); - await this.container.fileSyncOrchestrator.run(fileInPendingPaths); + try { + Logger.info('[SYNC ENGINE] Monitoring polling...'); + + const fileInPendingPaths = + (await this.container.virtualDrive.getPlaceholderWithStatePending()) as Array; + Logger.info('[SYNC ENGINE] fileInPendingPaths', fileInPendingPaths); + await this.container.fileSyncOrchestrator.run(fileInPendingPaths); + } catch (error) { + Logger.error('[SYNC ENGINE] Polling', error); + } } } diff --git a/src/apps/sync-engine/dependency-injection/files/builder.ts b/src/apps/sync-engine/dependency-injection/files/builder.ts index 63f758eee..a1ff33e77 100644 --- a/src/apps/sync-engine/dependency-injection/files/builder.ts +++ b/src/apps/sync-engine/dependency-injection/files/builder.ts @@ -27,6 +27,7 @@ import { DependencyInjectionHttpClientsProvider } from '../common/clients'; import { FileSyncronizer } from '../../../../context/virtual-drive/files/application/FileSyncronizer'; import { FilePlaceholderConverter } from '../../../../context/virtual-drive/files/application/FIlePlaceholderConverter'; import { FileSyncStatusUpdater } from '../../../../context/virtual-drive/files/application/FileSyncStatusUpdater'; +import { FileContentsUpdater } from '../../../../context/virtual-drive/files/application/FileContentsUpdater'; export async function buildFilesContainer( folderContainer: FoldersContainer, @@ -124,6 +125,11 @@ export async function buildFilesContainer( const fileSyncStatusUpdater = new FileSyncStatusUpdater(localFileSystem); + const fileContentsUpdater = new FileContentsUpdater( + repository, + remoteFileSystem + ); + const fileSyncronizer = new FileSyncronizer( repository, fileSyncStatusUpdater, @@ -132,6 +138,7 @@ export async function buildFilesContainer( sharedContainer.absolutePathToRelativeConverter, folderContainer.folderCreator, folderContainer.offline.folderCreator, + fileContentsUpdater, folderContainer.foldersFatherSyncStatusUpdater ); diff --git a/src/context/shared/domain/ServerFile.ts b/src/context/shared/domain/ServerFile.ts index 12e5e3a3e..5f70e3587 100644 --- a/src/context/shared/domain/ServerFile.ts +++ b/src/context/shared/domain/ServerFile.ts @@ -19,4 +19,5 @@ export type ServerFile = { userId: number; status: ServerFileStatus; plainName?: string; + uuid?: string; }; diff --git a/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts b/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts index 80e9afc13..d6f342379 100644 --- a/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts +++ b/src/context/virtual-drive/boundaryBridge/application/FileSyncOrchestrator.ts @@ -7,14 +7,12 @@ export class FileSyncOrchestrator { private readonly fileSyncronizer: FileSyncronizer ) {} - run(absolutePaths: string[]): Promise { - return Promise.all( - absolutePaths.map(async (absolutePath) => { - await this.fileSyncronizer.run( - absolutePath, - this.contentsUploader.run.bind(this.contentsUploader) - ); - }) - ); + async run(absolutePaths: string[]): Promise { + for (const absolutePath of absolutePaths) { + await this.fileSyncronizer.run( + absolutePath, + this.contentsUploader.run.bind(this.contentsUploader) + ); + } } } diff --git a/src/context/virtual-drive/files/application/FileContentsUpdater.ts b/src/context/virtual-drive/files/application/FileContentsUpdater.ts index e0a189bc9..809b368a7 100644 --- a/src/context/virtual-drive/files/application/FileContentsUpdater.ts +++ b/src/context/virtual-drive/files/application/FileContentsUpdater.ts @@ -1,13 +1,22 @@ -// import { FileRepository } from '../domain/FileRepository'; -// import { RemoteFileSystem } from '../domain/file-systems/RemoteFileSystem'; +import { File } from '../domain/File'; +import { FileRepository } from '../domain/FileRepository'; +import { RemoteFileSystem } from '../domain/file-systems/RemoteFileSystem'; +import Logger from 'electron-log'; -// export class FileContentsUpdater { -// constructor( -// private readonly repository: FileRepository, -// private readonly remote: RemoteFileSystem -// ) {} +export class FileContentsUpdater { + constructor( + private readonly repository: FileRepository, + private readonly remote: RemoteFileSystem + ) {} -// async run(file: File): Promise { -// await this.local.updateSyncStatus(file); -// } -// } + async run( + file: File, + contentsId: File['contentsId'], + size: File['size'] + ): Promise { + Logger.info('Replace', file, contentsId, size); + await this.remote.replace(file, contentsId, size); + Logger.info('Updated', file, contentsId, size); + return this.repository.updateContentsAndSize(file, contentsId, size); + } +} diff --git a/src/context/virtual-drive/files/application/FileCreatorFromServerFile.ts b/src/context/virtual-drive/files/application/FileCreatorFromServerFile.ts index 454a71441..dad957b45 100644 --- a/src/context/virtual-drive/files/application/FileCreatorFromServerFile.ts +++ b/src/context/virtual-drive/files/application/FileCreatorFromServerFile.ts @@ -14,5 +14,6 @@ export function createFileFromServerFile( updatedAt: server.updatedAt, path: relativePath, status: server.status, + uuid: server.uuid, }); } diff --git a/src/context/virtual-drive/files/application/FileSyncronizer.ts b/src/context/virtual-drive/files/application/FileSyncronizer.ts index f38038945..e3088f942 100644 --- a/src/context/virtual-drive/files/application/FileSyncronizer.ts +++ b/src/context/virtual-drive/files/application/FileSyncronizer.ts @@ -15,8 +15,12 @@ import { File } from '../domain/File'; import { FileSyncStatusUpdater } from './FileSyncStatusUpdater'; import { FilePlaceholderConverter } from './FIlePlaceholderConverter'; import { FoldersFatherSyncStatusUpdater } from '../../folders/application/FoldersFatherSyncStatusUpdater'; +import { FileContentsUpdater } from './FileContentsUpdater'; export class FileSyncronizer { + // queue of files to be uploaded + private filePath: string | undefined; + private foldersPathQueue: string[] = []; constructor( private readonly repository: FileRepository, private readonly fileSyncStatusUpdater: FileSyncStatusUpdater, @@ -25,13 +29,15 @@ export class FileSyncronizer { private readonly absolutePathToRelativeConverter: AbsolutePathToRelativeConverter, private readonly folderCreator: FolderCreator, private readonly offlineFolderCreator: OfflineFolderCreator, + private readonly fileContentsUpdater: FileContentsUpdater, private readonly foldersFatherSyncStatusUpdater: FoldersFatherSyncStatusUpdater ) {} async run( absolutePath: string, upload: (path: string) => Promise - ) { + ): Promise { + this.filePath = absolutePath; const win32RelativePath = this.absolutePathToRelativeConverter.run(absolutePath); @@ -45,9 +51,31 @@ export class FileSyncronizer { status: FileStatuses.EXISTS, }); + await this.sync( + existingFile, + absolutePath, + posixRelativePath, + path, + upload + ); + } + + private async sync( + existingFile: File | undefined, + absolutePath: string, + posixRelativePath: string, + path: FilePath, + upload: (path: string) => Promise + ) { if (existingFile) { if (this.hasDifferentSize(existingFile, absolutePath)) { - return; + const contents = await upload(posixRelativePath); + existingFile = await this.fileContentsUpdater.run( + existingFile, + contents.id, + contents.size + ); + Logger.info('existingFile ', existingFile); } await this.convertAndUpdateSyncStatus(existingFile); } else { @@ -62,6 +90,7 @@ export class FileSyncronizer { attemps = 3 ) => { try { + await new Promise((resolve) => setTimeout(resolve, 2000)); const fileContents = await upload(posixRelativePath); const createdFile = await this.fileCreator.run(filePath, fileContents); await this.convertAndUpdateSyncStatus(createdFile); @@ -93,15 +122,17 @@ export class FileSyncronizer { const posixDir = PlatformPathConverter.getFatherPathPosix(posixRelativePath); try { - await new Promise((resolve) => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 4000)); await this.runFolderCreator(posixDir); } catch (error) { Logger.error('Error creating folder father creation:', error); if (error instanceof FolderNotFoundError) { + this.foldersPathQueue.push(posixDir); // father created await this.createFolderFather(posixDir); // child created - await this.runFolderCreator(posixDir); + Logger.info('Creating child', posixDir); + await this.retryFolderCreation(posixDir); } else { Logger.error( 'Error creating folder father creation inside catch:', @@ -118,7 +149,22 @@ export class FileSyncronizer { } private async convertAndUpdateSyncStatus(file: File) { - await this.filePlaceholderConverter.run(file); - await this.fileSyncStatusUpdater.run(file); + await Promise.all([ + this.filePlaceholderConverter.run(file), + this.fileSyncStatusUpdater.run(file), + ]); } + + private retryFolderCreation = async (posixDir: string, attemps = 3) => { + try { + await new Promise((resolve) => setTimeout(resolve, 4000)); + await this.runFolderCreator(posixDir); + } catch (error) { + Logger.error('Error creating folder father creation:', error); + if (attemps > 0) { + await this.retryFolderCreation(posixDir, attemps - 1); + return; + } + } + }; } diff --git a/src/context/virtual-drive/files/domain/File.ts b/src/context/virtual-drive/files/domain/File.ts index e8f831024..0e501a749 100644 --- a/src/context/virtual-drive/files/domain/File.ts +++ b/src/context/virtual-drive/files/domain/File.ts @@ -15,6 +15,7 @@ import { FileRenamedDomainEvent } from './events/FileRenamedDomainEvent'; import { FilePlaceholderId, createFilePlaceholderId } from './PlaceholderId'; export type FileAttributes = { + uuid?: string; contentsId: string; folderId: number; createdAt: string; @@ -27,10 +28,11 @@ export type FileAttributes = { export class File extends AggregateRoot { private constructor( + private _uuid: string, private _contentsId: ContentsId, private _folderId: number, private _path: FilePath, - private readonly _size: FileSize, + private _size: FileSize, public createdAt: Date, public updatedAt: Date, private _status: FileStatus @@ -38,6 +40,10 @@ export class File extends AggregateRoot { super(); } + public get uuid(): string { + return this._uuid; + } + public get contentsId() { return this._contentsId.value; } @@ -80,6 +86,7 @@ export class File extends AggregateRoot { static from(attributes: FileAttributes): File { return new File( + attributes.uuid ?? '', new ContentsId(attributes.contentsId), attributes.folderId, new FilePath(attributes.path), @@ -97,6 +104,7 @@ export class File extends AggregateRoot { path: FilePath ): File { const file = new File( + '', // we should generate a uuid here new ContentsId(contentsId), folder.id, path, @@ -183,8 +191,17 @@ export class File extends AggregateRoot { return this._status.is(status); } + replaceContestsAndSize(contentsId: string, size: number) { + this._contentsId = new ContentsId(contentsId); + this._size = new FileSize(size); + this.updatedAt = new Date(); + + return this; + } + attributes(): FileAttributes { return { + uuid: this._uuid, contentsId: this.contentsId, folderId: this.folderId, createdAt: this.createdAt.toISOString(), diff --git a/src/context/virtual-drive/files/domain/FileRepository.ts b/src/context/virtual-drive/files/domain/FileRepository.ts index db12ab343..ef83cda81 100644 --- a/src/context/virtual-drive/files/domain/FileRepository.ts +++ b/src/context/virtual-drive/files/domain/FileRepository.ts @@ -10,4 +10,10 @@ export interface FileRepository { add(file: File): Promise; update(file: File): Promise; + + updateContentsAndSize( + file: File, + newContentsId: File['contentsId'], + newSize: File['size'] + ): Promise; } diff --git a/src/context/virtual-drive/files/domain/events/FileUpdateContentDomainEvent.ts b/src/context/virtual-drive/files/domain/events/FileUpdateContentDomainEvent.ts new file mode 100644 index 000000000..d5651d7bc --- /dev/null +++ b/src/context/virtual-drive/files/domain/events/FileUpdateContentDomainEvent.ts @@ -0,0 +1,40 @@ +import { DomainEvent } from '../../../../shared/domain/DomainEvent'; + +export type CreatedWebdavFileDomainEventAttributes = { + readonly size: number; + readonly type: string; + readonly path: string; +}; + +export class FileUpdateContentDomainEvent extends DomainEvent { + static readonly EVENT_NAME = 'file.update.content'; + + readonly size: number; + readonly contentId: string; + + constructor({ + aggregateId, + eventId, + size, + contentId, + }: { + aggregateId: string; + eventId?: string; + size: number; + contentId: string; + }) { + super({ + eventName: FileUpdateContentDomainEvent.EVENT_NAME, + aggregateId, + eventId, + }); + this.size = size; + this.contentId = contentId; + } + + toPrimitives() { + const { size, contentId } = this; + + return { size, contentId }; + } +} diff --git a/src/context/virtual-drive/files/domain/file-systems/RemoteFileSystem.ts b/src/context/virtual-drive/files/domain/file-systems/RemoteFileSystem.ts index 9573f252c..979af014c 100644 --- a/src/context/virtual-drive/files/domain/file-systems/RemoteFileSystem.ts +++ b/src/context/virtual-drive/files/domain/file-systems/RemoteFileSystem.ts @@ -9,4 +9,10 @@ export interface RemoteFileSystem { move(file: File): Promise; rename(file: File): Promise; + + replace( + file: File, + newContentsId: File['contentsId'], + newSize: File['size'] + ): Promise; } diff --git a/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts b/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts index cc7623820..92233f961 100644 --- a/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts +++ b/src/context/virtual-drive/files/infrastructure/InMemoryFileRepository.ts @@ -60,4 +60,19 @@ export class InMemoryFileRepository implements FileRepository { return this.add(file); } + + async updateContentsAndSize( + file: File, + newContentsId: File['contentsId'], + newSize: File['size'] + ): Promise { + if (!this.files.has(file.contentsId)) { + throw new Error('File not found'); + } + + const updatedFile = file.replaceContestsAndSize(newContentsId, newSize); + this.files.set(updatedFile.contentsId, updatedFile.attributes()); + this.files.delete(file.contentsId); + return updatedFile; + } } diff --git a/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts b/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts index 6208baced..7bc0facc9 100644 --- a/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts +++ b/src/context/virtual-drive/files/infrastructure/SDKRemoteFileSystem.ts @@ -85,4 +85,18 @@ export class SDKRemoteFileSystem implements RemoteFileSystem { bucketId: this.bucket, }); } + + async replace( + file: File, + newContentsId: File['contentsId'], + newSize: File['size'] + ): Promise { + await this.clients.newDrive.put( + `${process.env.NEW_DRIVE_URL}/drive/files/${file.uuid}`, + { + fileId: newContentsId, + size: newSize, + } + ); + } } diff --git a/src/context/virtual-drive/items/application/RemoteItemsGenerator.ts b/src/context/virtual-drive/items/application/RemoteItemsGenerator.ts index fb09c825e..e4f2510e6 100644 --- a/src/context/virtual-drive/items/application/RemoteItemsGenerator.ts +++ b/src/context/virtual-drive/items/application/RemoteItemsGenerator.ts @@ -32,6 +32,7 @@ export class RemoteItemsGenerator { updatedAt: updatedFile.updatedAt, userId: updatedFile.userId, status: updatedFile.status as ServerFileStatus, + uuid: updatedFile.uuid, }; });