diff --git a/src/app/drive/services/downloadManager.service.test.ts b/src/app/drive/services/downloadManager.service.test.ts index 9246b469c5..a7e6982a2c 100644 --- a/src/app/drive/services/downloadManager.service.test.ts +++ b/src/app/drive/services/downloadManager.service.test.ts @@ -615,6 +615,8 @@ describe('downloadManagerService', () => { taskId: mockTaskId, failedItems: [], }; + + const mockTaskStatusProgress = vi.fn((progress: number) => progress); const mockUpdateProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(() => 0); @@ -626,14 +628,18 @@ describe('downloadManagerService', () => { const handleConnectionLostSpy = vi.spyOn(DownloadManagerService.instance, 'handleConnectionLost'); const checkAndHandleConnectionLossSpy = vi.spyOn(DownloadManagerService.instance, 'checkAndHandleConnectionLost'); - await DownloadManagerService.instance.downloadFolder(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadFolder( + mockTask, + mockTaskStatusProgress, + mockUpdateProgress, + mockIncrementItemCount, + ); expect(updateTaskSpy).toHaveBeenCalledWith({ taskId: mockTaskId, merge: { status: TaskStatus.InProcess, progress: Infinity, - nItems: 0, }, }); expect(downloadFolderItemSpy).toHaveBeenCalledWith({ @@ -641,7 +647,8 @@ describe('downloadManagerService', () => { isSharedFolder: mockTask.options.areSharedItems, foldersIterator: mockTask.createFoldersIterator, filesIterator: mockTask.createFilesIterator, - updateProgress: expect.anything(), + updateProgress: mockTaskStatusProgress, + downloadProgress: mockUpdateProgress, updateNumItems: mockIncrementItemCount, options: { closeWhenFinished: true, @@ -678,6 +685,7 @@ describe('downloadManagerService', () => { }; const mockFile2: DriveFileData = { ...mockFile, id: 2, name: 'File2' }; + const mockTaskStatusProgress = vi.fn((progress: number) => progress); const mockUpdateProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(() => 0); @@ -692,7 +700,12 @@ describe('downloadManagerService', () => { const handleConnectionLostSpy = vi.spyOn(DownloadManagerService.instance, 'handleConnectionLost'); const checkAndHandleConnectionLossSpy = vi.spyOn(DownloadManagerService.instance, 'checkAndHandleConnectionLost'); - await DownloadManagerService.instance.downloadFolder(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadFolder( + mockTask, + mockTaskStatusProgress, + mockUpdateProgress, + mockIncrementItemCount, + ); expect(mockTask.failedItems).toEqual([mockFile, mockFile2]); expect(handleConnectionLostSpy).toHaveBeenCalledWith(5000); @@ -723,6 +736,7 @@ describe('downloadManagerService', () => { failedItems: [], }; + const mockTaskStatusProgress = vi.fn((progress: number) => progress); const mockUpdateProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(() => 0); @@ -736,7 +750,12 @@ describe('downloadManagerService', () => { const checkAndHandleConnectionLossSpy = vi.spyOn(DownloadManagerService.instance, 'checkAndHandleConnectionLost'); await expect( - DownloadManagerService.instance.downloadFolder(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadFolder( + mockTask, + mockTaskStatusProgress, + mockUpdateProgress, + mockIncrementItemCount, + ), ).rejects.toThrow(ErrorMessages.ServerUnavailable); expect(mockTask.failedItems).toEqual([]); @@ -766,6 +785,7 @@ describe('downloadManagerService', () => { failedItems: [], }; + const mockTaskStatusProgress = vi.fn((progress: number) => progress); const mockUpdateProgress = vi.fn(); const mockIncrementItemCount = vi.fn(); @@ -781,7 +801,12 @@ describe('downloadManagerService', () => { }); await expect( - DownloadManagerService.instance.downloadFolder(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadFolder( + mockTask, + mockTaskStatusProgress, + mockUpdateProgress, + mockIncrementItemCount, + ), ).rejects.toThrow(ConnectionLostError); expect(handleConnectionLostSpy).toHaveBeenCalledWith(5000); @@ -1029,6 +1054,7 @@ describe('downloadManagerService', () => { failedItems: [], }; const mockUpdateProgress = vi.fn((progress: number) => progress); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(() => 0); vi.spyOn(FlatFolderZip.prototype, 'abort').mockImplementation(() => {}); @@ -1043,15 +1069,21 @@ describe('downloadManagerService', () => { }); (downloadFolderAsZip as Mock).mockImplementation(downloadFolderSpy); - await DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ); expect(downloadFolderSpy).toHaveBeenNthCalledWith(1, { folder: mockFolder, isSharedFolder: mockTask.options.areSharedItems, foldersIterator: mockTask.createFoldersIterator, filesIterator: mockTask.createFilesIterator, + updateNumItems: expect.anything(), updateProgress: expect.anything(), - updateNumItems: mockIncrementItemCount, + downloadProgress: expect.anything(), options: { closeWhenFinished: false, ...mockTask.credentials, @@ -1064,8 +1096,9 @@ describe('downloadManagerService', () => { isSharedFolder: mockTask.options.areSharedItems, foldersIterator: mockTask.createFoldersIterator, filesIterator: mockTask.createFilesIterator, + updateNumItems: expect.anything(), updateProgress: expect.anything(), - updateNumItems: mockIncrementItemCount, + downloadProgress: expect.anything(), options: { closeWhenFinished: false, ...mockTask.credentials, @@ -1124,6 +1157,7 @@ describe('downloadManagerService', () => { vi.spyOn(LRUFilesCacheManager, 'getInstance').mockResolvedValue(lruCache); const mockUpdateProgress = vi.fn((progress: number) => progress); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(() => 0); const checkIfCachedSourceIsOlderSpy = vi.fn().mockReturnValueOnce(false).mockReturnValueOnce(true); @@ -1139,7 +1173,12 @@ describe('downloadManagerService', () => { const addFileZipSpy = vi.spyOn(FlatFolderZip.prototype, 'addFile').mockResolvedValue(); const closeZipSpy = vi.spyOn(FlatFolderZip.prototype, 'close').mockResolvedValue(); - await DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ); expect(addFileZipSpy).toHaveBeenNthCalledWith(1, `${mockFile.name}.${mockFile.type}`, expect.anything()); expect(addFileZipSpy).toHaveBeenNthCalledWith(2, mockFileCache.name, expect.anything()); @@ -1182,6 +1221,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1209,7 +1249,12 @@ describe('downloadManagerService', () => { const closeZipSpy = vi.spyOn(FlatFolderZip.prototype, 'close').mockResolvedValue(); await expect( - DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ), ).resolves.toBeUndefined(); expect(mockTask.failedItems).toEqual([mockFileCache]); @@ -1251,6 +1296,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1277,7 +1323,12 @@ describe('downloadManagerService', () => { const addFileZipSpy = vi.spyOn(FlatFolderZip.prototype, 'addFile').mockResolvedValueOnce(); const closeZipSpy = vi.spyOn(FlatFolderZip.prototype, 'close').mockResolvedValue(); - await DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ); expect(mockTask.failedItems).toContain(mockFile2); expect(addFileZipSpy).toHaveBeenCalledTimes(1); @@ -1286,6 +1337,58 @@ describe('downloadManagerService', () => { }); describe('downloadItems', () => { + test('When downloading files, then the downloaded progress should be updated correctly', async () => { + const mockTask: DownloadTask = { + abortController: new AbortController(), + items: [mockFile as DriveItemData], + createFilesIterator: createFilesIterator, + createFoldersIterator: createFoldersIterator, + credentials: { + credentials: { user: 'any-user', pass: 'any-pass' }, + mnemonic: 'any-mnemonic', + }, + options: { areSharedItems: false, downloadName: `${mockFile.name}.${mockFile.type}`, showErrors: true }, + taskId: 'mock-task-id', + failedItems: [], + }; + + const mockUpdateStatusProgress = vi.fn(); + const mockUpdateDownloadedProgress = vi.fn(); + const mockIncrementItemCount = vi.fn(); + + const levelsBlobsCache = new LevelsBlobsCache(); + const lruCache = new LRUCache(levelsBlobsCache, 1); + vi.spyOn(lruCache, 'get').mockResolvedValue({ id: mockFile.id, parentId: mockFile.folderId, source: undefined }); + vi.spyOn(LRUFilesCacheManager, 'getInstance').mockResolvedValue(lruCache); + (checkIfCachedSourceIsOlder as Mock).mockReturnValue(true); + + (downloadFile as Mock).mockImplementation(async ({ options }) => { + const { notifyProgress } = options; + if (notifyProgress) { + notifyProgress(100, 25); + notifyProgress(100, 50); + notifyProgress(100, 100); + } + return new ReadableStream(); + }); + + (binaryStreamToBlob as Mock).mockResolvedValue({ stream: () => new ReadableStream() }); + vi.spyOn(FlatFolderZip.prototype, 'addFile').mockResolvedValue(); + vi.spyOn(FlatFolderZip.prototype, 'close').mockResolvedValue(); + vi.spyOn(FlatFolderZip.prototype, 'abort').mockImplementation(() => {}); + + await DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateStatusProgress, + mockUpdateDownloadedProgress, + mockIncrementItemCount, + ); + + expect(mockUpdateDownloadedProgress).toHaveBeenCalledWith(25); + expect(mockUpdateDownloadedProgress).toHaveBeenCalledWith(25); + expect(mockUpdateDownloadedProgress).toHaveBeenCalledWith(50); + }); + test('When there is an empty file, then it should be added to the zip without downloading', async () => { const emptyFile: DriveFileData = { ...mockFile, id: 3, name: 'EmptyFile', size: 0 }; const mockTask: DownloadTask = { @@ -1310,6 +1413,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1327,7 +1431,12 @@ describe('downloadManagerService', () => { const addFileZipSpy = vi.spyOn(FlatFolderZip.prototype, 'addFile').mockImplementation(() => {}); const closeZipSpy = vi.spyOn(FlatFolderZip.prototype, 'close').mockResolvedValue(); - await DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ); expect(addFileZipSpy).toHaveBeenCalledWith(`${emptyFile.name}.${emptyFile.type}`, expect.anything()); expect(closeZipSpy).toHaveBeenCalled(); @@ -1359,6 +1468,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1385,7 +1495,12 @@ describe('downloadManagerService', () => { failedItems: [mockFile], }); - await DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ); expect(mockTask.failedItems).toContain(mockFile); expect(lruCacheSpy).toHaveBeenCalledTimes(1); @@ -1417,6 +1532,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1441,7 +1557,12 @@ describe('downloadManagerService', () => { const closeZipSpy = vi.spyOn(FlatFolderZip.prototype, 'close').mockRejectedValue(new ConnectionLostError()); await expect( - DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ), ).rejects.toThrow(ConnectionLostError); expect(closeZipSpy).toHaveBeenCalled(); @@ -1472,6 +1593,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1498,7 +1620,12 @@ describe('downloadManagerService', () => { const closeZipSpy = vi.spyOn(FlatFolderZip.prototype, 'close').mockResolvedValue(); await expect( - DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ), ).rejects.toThrow(ErrorMessages.ServerUnavailable); expect(closeZipSpy).toHaveBeenCalled(); @@ -1571,13 +1698,19 @@ describe('downloadManagerService', () => { failedItems: [], }; + const mockTaskStatusProgress = vi.fn((progress: number) => progress); const mockUpdateProgress = vi.fn(); const mockIncrementItemCount = vi.fn(); (downloadFolderAsZip as Mock).mockRejectedValueOnce(new Error('Folder download error')); await expect( - DownloadManagerService.instance.downloadFolder(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadFolder( + mockTask, + mockTaskStatusProgress, + mockUpdateProgress, + mockIncrementItemCount, + ), ).rejects.toThrow('Folder download error'); }); @@ -1636,6 +1769,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1669,7 +1803,12 @@ describe('downloadManagerService', () => { }); await expect( - DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ), ).rejects.toThrow(ConnectionLostError); expect(lruCacheSpy).toHaveBeenCalledTimes(2); expect(spyAddFile).toHaveBeenCalledTimes(1); @@ -1701,6 +1840,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); vi.spyOn(FlatFolderZip.prototype, 'abort').mockImplementation(() => {}); @@ -1712,7 +1852,12 @@ describe('downloadManagerService', () => { }); await expect( - DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount), + DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ), ).rejects.toThrow(ErrorMessages.ServerUnavailable); }); @@ -1739,6 +1884,7 @@ describe('downloadManagerService', () => { }; const mockUpdateProgress = vi.fn(); + const mockUpdateStatusProgress = vi.fn((progress: number) => progress); const mockIncrementItemCount = vi.fn(); const levelsBlobsCache = new LevelsBlobsCache(); @@ -1764,7 +1910,12 @@ describe('downloadManagerService', () => { allItemsFailed: true, }); - await DownloadManagerService.instance.downloadItems(mockTask, mockUpdateProgress, mockIncrementItemCount); + await DownloadManagerService.instance.downloadItems( + mockTask, + mockUpdateProgress, + mockUpdateStatusProgress, + mockIncrementItemCount, + ); expect(mockTask.failedItems).toContain(mockFolder); expect(lruCacheSpy).toHaveBeenCalledTimes(1); diff --git a/src/app/drive/services/downloadManager.service.ts b/src/app/drive/services/downloadManager.service.ts index 82ec911bcc..fd09a6ef32 100644 --- a/src/app/drive/services/downloadManager.service.ts +++ b/src/app/drive/services/downloadManager.service.ts @@ -214,6 +214,7 @@ export class DownloadManagerService { readonly downloadFolder = async ( downloadTask: DownloadTask, updateProgressCallback: (progress: number) => void, + updateDownloadedProgress: (progress: number) => void, incrementItemCount: () => void, ) => { const { connectionLost, cleanup } = this.handleConnectionLost(5000); @@ -226,7 +227,6 @@ export class DownloadManagerService { merge: { status: TaskStatus.InProcess, progress: Infinity, - nItems: 0, }, }); @@ -237,6 +237,7 @@ export class DownloadManagerService { foldersIterator: createFoldersIterator, filesIterator: createFilesIterator, updateProgress: updateProgressCallback, + downloadProgress: updateDownloadedProgress, updateNumItems: incrementItemCount, options: { closeWhenFinished: true, @@ -312,6 +313,7 @@ export class DownloadManagerService { readonly downloadItems = async ( downloadTask: DownloadTask, updateProgressCallback: (progress: number) => void, + updateDownloadedProgress: (progress: number) => void, incrementItemCount: () => void, ) => { const { connectionLost, cleanup } = this.handleConnectionLost(5000); @@ -320,10 +322,13 @@ export class DownloadManagerService { const folderZip = new FlatFolderZip(options.downloadName, { abortController }); const downloadProgress: number[] = []; + const lastReportedBytes: number[] = []; const failedItems: DownloadItemType[] = []; + let downloadedProgress = 0; items.forEach((_, index) => { downloadProgress[index] = 0; + lastReportedBytes[index] = 0; }); const calculateProgress = () => { @@ -356,6 +361,18 @@ export class DownloadManagerService { return; } + const notifyProgressCallback = (totalBytes: number, downloadedBytes: number) => { + const progress = downloadedBytes / totalBytes; + downloadProgress[index] = progress; + + const bytesDelta = downloadedBytes - lastReportedBytes[index]; + downloadedProgress += bytesDelta; + lastReportedBytes[index] = downloadedBytes; + + updateDownloadedProgress(bytesDelta); + updateProgressCallback(calculateProgress()); + }; + const downloadedFileStream = await downloadFile({ fileId: (driveItem as DriveFileData).fileId, bucketId: (driveItem as DriveFileData).bucket, @@ -366,13 +383,7 @@ export class DownloadManagerService { mnemonic: (driveItem as AdvancedSharedItem).credentials?.mnemonic ?? credentials.mnemonic, options: { abortController, - notifyProgress: (totalBytes, downloadedBytes) => { - const progress = downloadedBytes / totalBytes; - - downloadProgress[index] = progress; - - updateProgressCallback(calculateProgress()); - }, + notifyProgress: notifyProgressCallback, }, }); @@ -398,6 +409,7 @@ export class DownloadManagerService { downloadProgress[index] = progress; updateProgressCallback(calculateProgress()); }, + downloadProgress: updateDownloadedProgress, updateNumItems: incrementItemCount, options: { destination: folderZip, diff --git a/src/app/drive/services/folder.service.test.ts b/src/app/drive/services/folder.service.test.ts index aae86da977..fb60b98510 100644 --- a/src/app/drive/services/folder.service.test.ts +++ b/src/app/drive/services/folder.service.test.ts @@ -104,37 +104,77 @@ describe('Folder Service', () => { expect(downloadFile).not.toHaveBeenCalled(); }); - test('When the file has content and is not cached, then it should download the file', async () => { - const { getFileStream } = await import('./folder.service'); - - const mockLruCache = { - get: vi.fn().mockResolvedValue(undefined), - }; - vi.spyOn(LRUFilesCacheManager, 'getInstance').mockResolvedValue(mockLruCache as any); - - const mockDownloadedStream = new ReadableStream(); - vi.mocked(downloadFile).mockResolvedValue(mockDownloadedStream as any); - - const mockBlob = new Blob(['downloaded content']); - vi.mocked(binaryStreamToBlob).mockResolvedValue(mockBlob); - vi.mocked(updateDatabaseFileSourceData).mockResolvedValue(); - - const stream = await getFileStream({ - file: mockFile, - creds: { user: 'test-user', pass: 'test-pass' }, - mnemonic: 'test-mnemonic', + describe('The file is not empty and is not cached', () => { + test('When the download starts, the download progress of the file should be reported correctly', async () => { + const { getFileStream } = await import('./folder.service'); + + const mockDownloadProgress = vi.fn(); + const mockLruCache = { + get: vi.fn().mockResolvedValue(undefined), + }; + vi.spyOn(LRUFilesCacheManager, 'getInstance').mockResolvedValue(mockLruCache as any); + + const mockDownloadedStream = new ReadableStream(); + + // Simulate the download progress + vi.mocked(downloadFile).mockImplementation(async ({ options }) => { + if (options?.notifyProgress) { + options.notifyProgress(0, 25); + options.notifyProgress(0, 50); + options.notifyProgress(0, 75); + options.notifyProgress(0, 100); + } + return mockDownloadedStream as any; + }); + + const mockBlob = new Blob(['downloaded content']); + vi.mocked(binaryStreamToBlob).mockResolvedValue(mockBlob); + vi.mocked(updateDatabaseFileSourceData).mockResolvedValue(); + + await getFileStream({ + file: mockFile, + creds: { user: 'test-user', pass: 'test-pass' }, + mnemonic: 'test-mnemonic', + downloadProgress: mockDownloadProgress, + }); + + const totalBytesReported = mockDownloadProgress.mock.calls.reduce((sum, call) => sum + call[0], 0); + + expect(totalBytesReported).toBe(mockFile.size); }); - expect(stream).toBeInstanceOf(ReadableStream); - expect(downloadFile).toHaveBeenCalledWith({ - bucketId: mockFile.bucket, - fileId: mockFile.fileId, - creds: { user: 'test-user', pass: 'test-pass' }, - mnemonic: 'test-mnemonic', - options: { - notifyProgress: expect.any(Function), - abortController: undefined, - }, + test('When the download starts, then it should be completed successfully', async () => { + const { getFileStream } = await import('./folder.service'); + + const mockLruCache = { + get: vi.fn().mockResolvedValue(undefined), + }; + vi.spyOn(LRUFilesCacheManager, 'getInstance').mockResolvedValue(mockLruCache as any); + + const mockDownloadedStream = new ReadableStream(); + vi.mocked(downloadFile).mockResolvedValue(mockDownloadedStream as any); + + const mockBlob = new Blob(['downloaded content']); + vi.mocked(binaryStreamToBlob).mockResolvedValue(mockBlob); + vi.mocked(updateDatabaseFileSourceData).mockResolvedValue(); + + const stream = await getFileStream({ + file: mockFile, + creds: { user: 'test-user', pass: 'test-pass' }, + mnemonic: 'test-mnemonic', + }); + + expect(stream).toBeInstanceOf(ReadableStream); + expect(downloadFile).toHaveBeenCalledWith({ + bucketId: mockFile.bucket, + fileId: mockFile.fileId, + creds: { user: 'test-user', pass: 'test-pass' }, + mnemonic: 'test-mnemonic', + options: { + notifyProgress: expect.any(Function), + abortController: undefined, + }, + }); }); }); }); diff --git a/src/app/drive/services/folder.service.ts b/src/app/drive/services/folder.service.ts index 85c9a82eb3..12757d8d81 100644 --- a/src/app/drive/services/folder.service.ts +++ b/src/app/drive/services/folder.service.ts @@ -34,6 +34,7 @@ export interface GetFileStreamParams { creds: { user: string; pass: string }; mnemonic: string; abortController?: AbortController; + downloadProgress?: (progress: number) => void; } export interface IFolders { @@ -227,12 +228,14 @@ export async function getFileStream({ creds, mnemonic, abortController, + downloadProgress, }: GetFileStreamParams): Promise> { const lruFilesCacheManager = await LRUFilesCacheManager.getInstance(); const cachedFile = await lruFilesCacheManager.get(file.id?.toString()); const isCachedFileOlder = checkIfCachedSourceIsOlder({ cachedFile, file }); if (cachedFile?.source && !isCachedFileOlder) { + downloadProgress?.(file.size); return cachedFile.source.stream(); } @@ -240,13 +243,18 @@ export async function getFileStream({ return new Blob([]).stream(); } + let lastReportedProgress = 0; const downloadedFileStream = await downloadFile({ bucketId: file.bucket, fileId: file.fileId, creds, mnemonic, options: { - notifyProgress: () => {}, + notifyProgress: (_, progress) => { + const progressDelta = progress - lastReportedProgress; + downloadProgress?.(progressDelta); + lastReportedProgress = progress; + }, abortController, }, }); @@ -279,6 +287,7 @@ export async function downloadFolderAsZip({ updateNumItems, options, abortController, + downloadProgress, }: { folder: DriveFolderData; isSharedFolder: boolean; @@ -288,6 +297,7 @@ export async function downloadFolderAsZip({ updateNumItems: () => void; options: DownloadFolderAsZipOptions; abortController?: AbortController; + downloadProgress?: (progress: number) => void; }): Promise<{ totalItems: DownloadFilesType; failedItems: DownloadFilesType; @@ -319,7 +329,7 @@ export async function downloadFolderAsZip({ const downloadQueue: QueueObject = queue((folderToDownload, next: (err?: Error) => void) => { if (abortController?.signal.aborted) return next(new Error('Download aborted')); - const newConcurrency = QueueUtilsService.instance.getConcurrencyUsingPerfomance( + const newConcurrency = QueueUtilsService.instance.getConcurrencyUsingPerformance( downloadQueue.concurrency, maxConcurrency, ); @@ -365,6 +375,7 @@ export async function downloadFolderAsZip({ creds: options.credentials, mnemonic: options.mnemonic, abortController, + downloadProgress, }); } catch (error: unknown) { if (isLostConnectionError(error)) { diff --git a/src/app/network/DownloadManager.test.ts b/src/app/network/DownloadManager.test.ts index e13ee40218..ae8f457d56 100644 --- a/src/app/network/DownloadManager.test.ts +++ b/src/app/network/DownloadManager.test.ts @@ -11,7 +11,7 @@ import { createFilesIterator, createFoldersIterator } from 'app/drive/services/f import { DriveFileData, DriveFolderData, DriveItemData } from 'app/drive/types'; import tasksService from 'app/tasks/services/tasks.service'; import { QueueUtilsService } from 'utils/queueUtils'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { DownloadManager } from './DownloadManager'; import { EncryptionVersion, FileStatus } from '@internxt/sdk/dist/drive/storage/types'; import { ConnectionLostError } from './requests'; @@ -28,22 +28,6 @@ vi.mock('app/drive/services/folder.service', () => ({ createFoldersIterator: vi.fn(), })); -vi.mock('services/error.service', () => ({ - default: { - castError: vi.fn().mockImplementation((e) => ({ message: e.message ?? 'Default error message' })), - reportError: vi.fn(), - }, -})); - -vi.mock('app/tasks/services/tasks.service', () => ({ - default: { - updateTask: vi.fn(), - findTask: vi.fn(), - addListener: vi.fn(), - removeListener: vi.fn(), - }, -})); - vi.mock('i18next', () => ({ t: () => MOCK_TRANSLATION_MESSAGE })); vi.mock('app/drive/services/downloadManager.service', () => ({ @@ -65,6 +49,10 @@ describe('downloadManager', () => { vi.resetModules(); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it('should generate task for a folder and download it using the queue', async () => { const mockFolder: DriveFolderData = { id: 0, @@ -113,7 +101,7 @@ describe('downloadManager', () => { }; vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(6); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(6); vi.spyOn(tasksService, 'addListener').mockReturnValue(); vi.spyOn(tasksService, 'removeListener').mockReturnValue(); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); @@ -130,7 +118,7 @@ describe('downloadManager', () => { await DownloadManager.downloadItem(downloadItem); expect(downloadFolderSpy).toHaveBeenCalledOnce(); - expect(downloadFolderSpy).toHaveBeenCalledWith(mockTask, expect.anything(), expect.anything()); + expect(downloadFolderSpy).toHaveBeenCalledWith(mockTask, expect.anything(), expect.anything(), expect.anything()); expect(downloadFileSpy).not.toHaveBeenCalled(); expect(downloadItemsSpy).not.toHaveBeenCalled(); }); @@ -185,7 +173,7 @@ describe('downloadManager', () => { }; vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(6); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(6); vi.spyOn(tasksService, 'addListener').mockReturnValue(); vi.spyOn(tasksService, 'removeListener').mockReturnValue(); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); @@ -278,7 +266,7 @@ describe('downloadManager', () => { }; vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(6); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(6); vi.spyOn(tasksService, 'addListener').mockReturnValue(); vi.spyOn(tasksService, 'removeListener').mockReturnValue(); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); @@ -295,7 +283,7 @@ describe('downloadManager', () => { await DownloadManager.downloadItem(downloadItem); expect(downloadItemsSpy).toHaveBeenCalledOnce(); - expect(downloadItemsSpy).toHaveBeenCalledWith(mockTask, expect.anything(), expect.anything()); + expect(downloadItemsSpy).toHaveBeenCalledWith(mockTask, expect.anything(), expect.anything(), expect.anything()); expect(downloadFolderSpy).not.toHaveBeenCalled(); expect(downloadFileSpy).not.toHaveBeenCalled(); }); @@ -350,7 +338,7 @@ describe('downloadManager', () => { }; const newConcurrency = DownloadManager.downloadQueue.concurrency + 1; - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(newConcurrency); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(newConcurrency); vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); vi.spyOn(tasksService, 'addListener').mockReturnValue(); @@ -420,7 +408,7 @@ describe('downloadManager', () => { }; const newConcurrency = DownloadManager.downloadQueue.concurrency + 1; - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(newConcurrency); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(newConcurrency); vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); vi.spyOn(tasksService, 'addListener').mockReturnValue(); @@ -494,7 +482,7 @@ describe('downloadManager', () => { }; vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(6); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(6); vi.spyOn(tasksService, 'addListener').mockReturnValue(); vi.spyOn(tasksService, 'removeListener').mockReturnValue(); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); @@ -573,7 +561,7 @@ describe('downloadManager', () => { }; vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(6); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(6); vi.spyOn(tasksService, 'addListener').mockReturnValue(); vi.spyOn(tasksService, 'removeListener').mockReturnValue(); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); @@ -651,7 +639,7 @@ describe('downloadManager', () => { }; vi.spyOn(DownloadManagerService.instance, 'generateTasksForItem').mockResolvedValue(mockTask); - vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerfomance').mockReturnValue(6); + vi.spyOn(QueueUtilsService.instance, 'getConcurrencyUsingPerformance').mockReturnValue(6); vi.spyOn(tasksService, 'addListener').mockReturnValue(); vi.spyOn(tasksService, 'removeListener').mockReturnValue(); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); @@ -677,7 +665,7 @@ describe('downloadManager', () => { expect(errorServiceSpy).toHaveBeenCalledWith(testError); expect(downloadFileSpy).not.toHaveBeenCalled(); - expect(downloadFolderSpy).toHaveBeenCalledWith(mockTask, expect.anything(), expect.anything()); + expect(downloadFolderSpy).toHaveBeenCalledWith(mockTask, expect.anything(), expect.anything(), expect.anything()); expect(downloadItemsSpy).not.toHaveBeenCalled(); }); @@ -806,6 +794,95 @@ describe('downloadManager', () => { }); expect(downloadItemsSpy).toHaveBeenCalled(); }); + + it('When downloading a folder, then the task should be updated with the downloaded progress', async () => { + vi.spyOn(tasksService, 'findTask').mockReturnValue({ + id: 'task-folder', + status: TaskStatus.InProcess, + } as TaskData); + vi.spyOn(DownloadManagerService.instance, 'downloadFolder').mockImplementation( + async (_task, _updateStatusProgress, updateDownloadedProgress) => { + updateDownloadedProgress(100); + updateDownloadedProgress(200); + updateDownloadedProgress(150); + }, + ); + const updateTaskSpy = vi.spyOn(tasksService, 'updateTask'); + + const downloadTask = { + items: [{ id: 1, isFolder: true }] as DownloadItemType[], + taskId: 'task-folder', + abortController: new AbortController(), + options: { downloadName: 'test', areSharedItems: false, showErrors: true }, + credentials: { credentials: { user: 'test', pass: 'test' }, mnemonic: 'test' }, + createFilesIterator, + createFoldersIterator, + failedItems: [], + } as DownloadTask; + + await DownloadManager['downloadTask'](downloadTask); + + expect(updateTaskSpy).toHaveBeenCalledWith({ taskId: 'task-folder', merge: { downloadedProgress: 100 } }); + expect(updateTaskSpy).toHaveBeenCalledWith({ taskId: 'task-folder', merge: { downloadedProgress: 300 } }); + expect(updateTaskSpy).toHaveBeenCalledWith({ taskId: 'task-folder', merge: { downloadedProgress: 450 } }); + }); + + it('When downloading multiple items, then the task should be updated with the downloaded progress', async () => { + vi.spyOn(tasksService, 'findTask').mockReturnValue({ + id: 'task-items', + status: TaskStatus.InProcess, + } as TaskData); + vi.spyOn(DownloadManagerService.instance, 'downloadItems').mockImplementation( + async (_task, _updateStatusProgress, updateDownloadedProgress) => { + updateDownloadedProgress(50); + updateDownloadedProgress(75); + }, + ); + const updateTaskSpy = vi.spyOn(tasksService, 'updateTask'); + + const downloadTask = { + items: [ + { id: 1, isFolder: false }, + { id: 2, isFolder: true }, + ] as DownloadItemType[], + taskId: 'task-items', + abortController: new AbortController(), + options: { downloadName: 'test', areSharedItems: false, showErrors: true }, + credentials: { credentials: { user: 'test', pass: 'test' }, mnemonic: 'test' }, + createFilesIterator, + createFoldersIterator, + failedItems: [], + } as DownloadTask; + + await DownloadManager['downloadTask'](downloadTask); + + expect(updateTaskSpy).toHaveBeenCalledWith({ taskId: 'task-items', merge: { downloadedProgress: 50 } }); + expect(updateTaskSpy).toHaveBeenCalledWith({ taskId: 'task-items', merge: { downloadedProgress: 125 } }); + }); + + it('When the task is cancelled, then the task should not be updated with the downloaded progress', async () => { + vi.spyOn(tasksService, 'findTask').mockReturnValue({ id: 'task-4', status: TaskStatus.Cancelled } as TaskData); + const downloadFolderSpy = vi.spyOn(DownloadManagerService.instance, 'downloadFolder'); + const updateTaskSpy = vi.spyOn(tasksService, 'updateTask'); + + const downloadTask = { + items: [{ id: 1, isFolder: true }] as DownloadItemType[], + taskId: 'task-4', + abortController: new AbortController(), + options: { downloadName: 'test', areSharedItems: false, showErrors: true }, + credentials: { credentials: { user: 'test', pass: 'test' }, mnemonic: 'test' }, + createFilesIterator, + createFoldersIterator, + failedItems: [], + } as DownloadTask; + + await DownloadManager['downloadTask'](downloadTask); + + expect(downloadFolderSpy).not.toHaveBeenCalled(); + expect(updateTaskSpy).not.toHaveBeenCalledWith( + expect.objectContaining({ merge: expect.objectContaining({ downloadedProgress: expect.anything() }) }), + ); + }); }); describe('removeRetryItems', () => { @@ -920,6 +997,10 @@ describe('downloadManager', () => { options: { showErrors: true }, } as DownloadTask; + vi.spyOn(tasksService, 'findTask').mockReturnValue({ + id: mockTaskId, + status: TaskStatus.InProcess, + } as TaskData); const updateTaskSpy = vi.spyOn(tasksService, 'updateTask'); const retryTaskSpy = vi.spyOn(retryManager, 'getTasksById').mockReturnValueOnce([]); @@ -942,6 +1023,10 @@ describe('downloadManager', () => { options: { showErrors: true }, } as DownloadTask; + vi.spyOn(tasksService, 'findTask').mockReturnValue({ + id: mockTaskId, + status: TaskStatus.InProcess, + } as TaskData); const updateTaskSpy = vi.spyOn(tasksService, 'updateTask'); const retryTaskSpy = vi.spyOn(retryManager, 'getTasksById').mockReturnValueOnce([ { @@ -1025,6 +1110,11 @@ describe('downloadManager', () => { items: mockItems, options: { showErrors: true }, } as DownloadTask; + + vi.spyOn(tasksService, 'findTask').mockReturnValue({ + id: mockTaskId, + status: TaskStatus.InProcess, + } as TaskData); const notificationsServiceSpy = vi.spyOn(notificationsService, 'show'); DownloadManager['reportError'](mockError, mockDownloadTask); diff --git a/src/app/network/DownloadManager.ts b/src/app/network/DownloadManager.ts index baa5a1648c..0a4d91904e 100644 --- a/src/app/network/DownloadManager.ts +++ b/src/app/network/DownloadManager.ts @@ -41,7 +41,7 @@ export class DownloadManager { (downloadTask, next: (err?: Error) => void) => { if (downloadTask.abortController?.signal.aborted) return next(new Error('Download aborted')); - const newConcurrency = QueueUtilsService.instance.getConcurrencyUsingPerfomance( + const newConcurrency = QueueUtilsService.instance.getConcurrencyUsingPerformance( this.downloadQueue.concurrency, DownloadManager.MAX_CONCURRENT_DOWNLOADS, ); @@ -83,6 +83,7 @@ export class DownloadManager { */ private static readonly downloadTask = async (downloadTask: DownloadTask) => { const { items, taskId, abortController } = downloadTask; + let downloadedProgress = 0; const task = tasksService.findTask(taskId); if (task?.status === TaskStatus.Cancelled || abortController?.signal.aborted) { @@ -98,13 +99,25 @@ export class DownloadManager { tasksService.addListener({ event: TaskEvent.TaskCancelled, listener: cancelTaskListener }); try { - const updateProgressCallback = (progress: number) => { + const updateStatusTaskProgress = (taskStatusProgress: number) => { if (task?.status !== TaskStatus.Cancelled) { tasksService.updateTask({ taskId, merge: { status: TaskStatus.InProcess, - progress, + progress: taskStatusProgress, + }, + }); + } + }; + + const updateDownloadedProgress = (progress: number) => { + downloadedProgress += progress; + if (task?.status !== TaskStatus.Cancelled) { + tasksService.updateTask({ + taskId, + merge: { + downloadedProgress, }, }); } @@ -115,8 +128,8 @@ export class DownloadManager { tasksService.updateTask({ taskId, merge: { + downloadedProgress, status: TaskStatus.InProcess, - nItems: (task?.nItems ?? 0) + 1, }, }); } @@ -130,11 +143,21 @@ export class DownloadManager { }); if (items.length > 1) { - await DownloadManagerService.instance.downloadItems(downloadTask, updateProgressCallback, incrementItemCount); + await DownloadManagerService.instance.downloadItems( + downloadTask, + updateStatusTaskProgress, + updateDownloadedProgress, + incrementItemCount, + ); } else if (items[0].isFolder) { - await DownloadManagerService.instance.downloadFolder(downloadTask, updateProgressCallback, incrementItemCount); + await DownloadManagerService.instance.downloadFolder( + downloadTask, + updateStatusTaskProgress, + updateDownloadedProgress, + incrementItemCount, + ); } else { - await DownloadManagerService.instance.downloadFile(downloadTask, updateProgressCallback); + await DownloadManagerService.instance.downloadFile(downloadTask, updateStatusTaskProgress); } if (downloadTask.failedItems && downloadTask.failedItems.length > 0) { diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index f5167c0489..a0ccac57c7 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -163,7 +163,7 @@ export class UploadFoldersManager { (task, next: (err: Error | null, res?: DriveFolderData) => void) => { if (this.abortController?.signal.aborted) return; - const newConcurrency = QueueUtilsService.instance.getConcurrencyUsingPerfomance( + const newConcurrency = QueueUtilsService.instance.getConcurrencyUsingPerformance( this.uploadFoldersQueue.concurrency, UploadFoldersManager.MAX_CONCURRENT_UPLOADS, ); diff --git a/src/app/tasks/components/TaskLoggerActions/TaskButtonActionBlocks.tsx b/src/app/tasks/components/TaskLoggerActions/TaskButtonActionBlocks.tsx index 7537c9a61b..bc4f527f68 100644 --- a/src/app/tasks/components/TaskLoggerActions/TaskButtonActionBlocks.tsx +++ b/src/app/tasks/components/TaskLoggerActions/TaskButtonActionBlocks.tsx @@ -11,10 +11,11 @@ import RestartIcon from '../../../../assets/icons/tasklogger/circle-arrow.svg?re import WarningIcon from '../../../../assets/icons/tasklogger/warning.svg?react'; import { TaskLoggerButton } from '../TaskLoggerButton/TaskLoggerButton'; import { t } from 'i18next'; +import { bytesToString } from 'app/drive/services/size.service'; interface UploadingBlockProps { progressPercentage: string; - nItems: string; + downloadedProgress?: number; } const UploadingBlock = ({ progressPercentage }: UploadingBlockProps): JSX.Element => { @@ -30,12 +31,12 @@ const UploadingBlock = ({ progressPercentage }: UploadingBlockProps): JSX.Elemen ); }; -const DownloadingBlock = ({ progressPercentage, nItems }: UploadingBlockProps): JSX.Element => { +const DownloadingBlock = ({ progressPercentage, downloadedProgress }: UploadingBlockProps): JSX.Element => { return (
-
+
- {nItems && +nItems > 0 ? `${nItems}` : `${progressPercentage}%`} + {downloadedProgress ? `${bytesToString(downloadedProgress)}` : `${progressPercentage}%`}
@@ -48,8 +49,8 @@ const DownloadingBlock = ({ progressPercentage, nItems }: UploadingBlockProps): const PauseBlock = ({ isHovered, progress, - nItems, cancelAction, + downloadedProgress, taskType, pauseAction, showPauseButton, @@ -62,7 +63,7 @@ const PauseBlock = ({ {taskType.includes('upload') && showPauseButton && }
) : ( - + ); }; diff --git a/src/app/tasks/components/TaskLoggerActions/TaskLoggerActions.tsx b/src/app/tasks/components/TaskLoggerActions/TaskLoggerActions.tsx index 7540230971..e924d46b02 100644 --- a/src/app/tasks/components/TaskLoggerActions/TaskLoggerActions.tsx +++ b/src/app/tasks/components/TaskLoggerActions/TaskLoggerActions.tsx @@ -17,7 +17,7 @@ type TaskLoggerActionsProps = { isHovered: boolean; status: string; progress: string; - nItems: string; + downloadedProgress?: number; cancelAction: () => void; retryAction: () => void; taskType: TaskType; @@ -57,7 +57,7 @@ export const TaskLoggerActions = ({ isHovered, status, progress, - nItems, + downloadedProgress, cancelAction, retryAction, taskType, @@ -72,7 +72,6 @@ export const TaskLoggerActions = ({ { cancelAction(); removeUpload(taskId); @@ -82,6 +81,7 @@ export const TaskLoggerActions = ({ resumeAction={() => { resumeUpload(taskId); }} + downloadedProgress={downloadedProgress} magnifyingAction={openItemAction} infoAction={openRetryItemsAction} taskType={taskType} diff --git a/src/app/tasks/components/TaskLoggerItem/TaskLoggerItem.tsx b/src/app/tasks/components/TaskLoggerItem/TaskLoggerItem.tsx index afffd8c046..5cd9d04e70 100644 --- a/src/app/tasks/components/TaskLoggerItem/TaskLoggerItem.tsx +++ b/src/app/tasks/components/TaskLoggerItem/TaskLoggerItem.tsx @@ -181,7 +181,7 @@ const TaskLoggerItem = ({ notification, task, filesToRetry }: TaskLoggerItemProp isHovered={isHovered} status={notification.status} progress={progress.toString()} - nItems={notification.nItems?.toString() ?? '0'} + downloadedProgress={notification.downloadedProgress} cancelAction={onCancelButtonClicked} retryAction={handleRetryClick} taskType={notification.action} diff --git a/src/app/tasks/services/tasks.service/index.ts b/src/app/tasks/services/tasks.service/index.ts index 4f35df712b..efdd52cd5a 100644 --- a/src/app/tasks/services/tasks.service/index.ts +++ b/src/app/tasks/services/tasks.service/index.ts @@ -99,7 +99,7 @@ class TaskManagerService { subtitle: this.getTaskNotificationSubtitle(task), icon: this.getTaskNotificationIcon(task), progress: task.progress, - nItems: task.nItems, + downloadedProgress: task.downloadedProgress, isTaskCancellable: task.cancellable, itemUUID: task?.itemUUID, }; diff --git a/src/app/tasks/types.ts b/src/app/tasks/types.ts index 845d6dbc11..b213d66f5c 100644 --- a/src/app/tasks/types.ts +++ b/src/app/tasks/types.ts @@ -46,9 +46,9 @@ export interface BaseTask { action: TaskType; status: TaskStatus; progress: number; - nItems?: number; cancellable: boolean; showNotification: boolean; + downloadedProgress?: number; subtitle?: string; stop?: () => Promise; } @@ -181,7 +181,7 @@ export interface TaskNotification { subtitle: string; icon: FunctionComponent>; progress: number; - nItems?: number; + downloadedProgress?: number; isTaskCancellable: boolean; } @@ -192,5 +192,6 @@ export interface TaskFilter { export interface UpdateTaskPayload { taskId: string; + downloadedProgress?: number; merge: Partial; } diff --git a/src/utils/queueUtils.test.ts b/src/utils/queueUtils.test.ts index 58d3ca35f3..8f84fcf5c0 100644 --- a/src/utils/queueUtils.test.ts +++ b/src/utils/queueUtils.test.ts @@ -21,30 +21,30 @@ describe('QueueUtilsService', () => { consoleWarnSpy.mockRestore(); }); - describe('getConcurrencyUsingPerfomance', () => { + describe('Getting the concurrency depending on the browser performance', () => { it('should increase concurrency when memory < 70%, respecting max limit', () => { mockMemory(60, 100); - expect(service.getConcurrencyUsingPerfomance(5, 10)).toBe(6); + expect(service.getConcurrencyUsingPerformance(5, 10)).toBe(6); expect(consoleWarnSpy).toHaveBeenCalledWith('Memory usage under 70%. Increasing queue concurrency to 6'); consoleWarnSpy.mockClear(); - expect(service.getConcurrencyUsingPerfomance(10, 10)).toBe(10); + expect(service.getConcurrencyUsingPerformance(10, 10)).toBe(10); expect(consoleWarnSpy).not.toHaveBeenCalled(); }); it('should decrease concurrency when memory >= 80%, not going below 1', () => { mockMemory(80, 100); - expect(service.getConcurrencyUsingPerfomance(5, 10)).toBe(4); + expect(service.getConcurrencyUsingPerformance(5, 10)).toBe(4); expect(consoleWarnSpy).toHaveBeenCalledWith('Memory usage reached 80%. Reducing folder upload concurrency.'); consoleWarnSpy.mockClear(); - expect(service.getConcurrencyUsingPerfomance(1, 10)).toBe(1); + expect(service.getConcurrencyUsingPerformance(1, 10)).toBe(1); expect(consoleWarnSpy).not.toHaveBeenCalled(); }); it('should maintain concurrency when memory is between 70-80%', () => { mockMemory(75, 100); - expect(service.getConcurrencyUsingPerfomance(5, 10)).toBe(5); + expect(service.getConcurrencyUsingPerformance(5, 10)).toBe(5); expect(consoleWarnSpy).not.toHaveBeenCalled(); }); @@ -55,7 +55,7 @@ describe('QueueUtilsService', () => { configurable: true, }); - expect(service.getConcurrencyUsingPerfomance(5, 10)).toBe(5); + expect(service.getConcurrencyUsingPerformance(5, 10)).toBe(5); expect(consoleWarnSpy).toHaveBeenCalledWith('Memory usage control is not available'); }); @@ -67,7 +67,7 @@ describe('QueueUtilsService', () => { configurable: true, }); - expect(service.getConcurrencyUsingPerfomance(5, 10)).toBe(5); + expect(service.getConcurrencyUsingPerformance(5, 10)).toBe(5); }); }); }); diff --git a/src/utils/queueUtils.ts b/src/utils/queueUtils.ts index 8c03c85a44..7cf2369756 100644 --- a/src/utils/queueUtils.ts +++ b/src/utils/queueUtils.ts @@ -1,7 +1,7 @@ export class QueueUtilsService { public static readonly instance: QueueUtilsService = new QueueUtilsService(); - public readonly getConcurrencyUsingPerfomance = (currentConcurrency: number, maxConcurrency: number): number => { + public readonly getConcurrencyUsingPerformance = (currentConcurrency: number, maxConcurrency: number): number => { let newConcurrency = currentConcurrency; try { const memory = window?.performance?.memory;