From 308ec3dcc0ff3806777b89dc99fef669c993bcf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:55:09 +0100 Subject: [PATCH] fix: use upload and download abort signals (#212) * fix: use upload and download abort signals * fix: progress trackers chore: update fm-lib and bee-js * chore: bump-up fm-lib version --- package.json | 4 +- pnpm-lock.yaml | 33 ++++---- .../CreateDriveModal/CreateDriveModal.tsx | 2 + .../components/FileBrowser/FileBrowser.tsx | 43 ++++++++--- .../FileBrowser/FileBrowserModals.tsx | 17 ++++ .../FileBrowser/FileItem/FileItem.tsx | 9 +-- .../components/InitialModal/InitialModal.tsx | 2 + .../UpgradeDriveModal/UpgradeDriveModal.tsx | 14 ++-- .../VersionList/VersionList.tsx | 7 +- .../filemanager/hooks/useBulkActions.ts | 19 +++-- src/modules/filemanager/hooks/useTransfers.ts | 11 ++- src/modules/filemanager/utils/download.ts | 77 ++++++++++--------- vite.config.mts | 1 + 13 files changed, 154 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index 319cf35..d0429b0 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,11 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@ethersphere/bee-js": "^10.4.0", + "@ethersphere/bee-js": "^11.1.1", "@formbricks/js": "^4.3.0", "@mui/icons-material": "^7.3.7", "@mui/material": "^7.3.7", - "@solarpunkltd/file-manager-lib": "^1.0.5", + "@solarpunkltd/file-manager-lib": "^1.0.7", "axios": "^0.30.2", "bignumber.js": "^9.3.1", "dotted-map": "^2.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebc37b3..a755e15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^11.14.1 version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4) '@ethersphere/bee-js': - specifier: ^10.4.0 - version: 10.4.0 + specifier: ^11.1.1 + version: 11.1.1 '@formbricks/js': specifier: ^4.3.0 version: 4.3.0 @@ -27,8 +27,8 @@ importers: specifier: ^7.3.7 version: 7.3.8(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@solarpunkltd/file-manager-lib': - specifier: ^1.0.5 - version: 1.0.5 + specifier: ^1.0.7 + version: 1.0.7 axios: specifier: ^0.30.2 version: 0.30.2(debug@4.4.3) @@ -1256,8 +1256,8 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ethersphere/bee-js@10.4.0': - resolution: {integrity: sha512-dtnyvKA3prBj22k2mfZWPq+uiK1hLMKr/jrGX4vaS41JsHX9uJ1GMGr4vCc41ANstGUGbp1dwCUWsXkqqjWuvg==} + '@ethersphere/bee-js@11.1.1': + resolution: {integrity: sha512-s2IpuVpy74cJyhj5yugO3+c9eR/r9TZsVJABk2iHWtTVkoA1qZdvgUjzV0w962o1gXrmYdxX4NOmlyIEpY11iQ==} engines: {bee: 2.4.0-390a402e, beeApiVersion: 7.2.0} '@formbricks/js@4.3.0': @@ -1833,9 +1833,9 @@ packages: '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} - '@solarpunkltd/file-manager-lib@1.0.5': - resolution: {integrity: sha512-s/2HP/VCrdoWOAZfZ7gl+maxO4D8wR3INdz9XSIjjvwlWU/KBUV3Vke5ANRuy28ikyDT4qOggu4G69EBHrl5YA==} - engines: {bee: 2.6.0, node: '>=20.0.0', npm: '>=11.0.0', pnpm: '>=10.0.0'} + '@solarpunkltd/file-manager-lib@1.0.7': + resolution: {integrity: sha512-L/mE2F/4oL24PHUNcSET2TlvGpuVlm8AFedpBl/2O59ISrBvLzs6H2jep5LytgRdwaD+Hpot2ssGkWKgHnycrQ==} + engines: {bee: 2.7.0, node: '>=24.0.0', npm: '>=11.0.0', pnpm: '>=10.0.0'} '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} @@ -2624,8 +2624,8 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cafe-utility@33.6.0: - resolution: {integrity: sha512-MyXjck4cf6+WahnRWpS083qA0Ib9XAgSVOfdd+mSwAWiyK0fMs0+khXHTLHTRA+nIZIJFVDEcMX8Cnc8G8NEDw==} + cafe-utility@33.8.0: + resolution: {integrity: sha512-qfbG8nHTY5jBfCSedG9+b57avhKePdiHfj2w0FMGokJuQU+3dxeT9+qasMReoXHBDp38CFHMSEvz5X0SDCXbWA==} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} @@ -6909,10 +6909,10 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@ethersphere/bee-js@10.4.0': + '@ethersphere/bee-js@11.1.1': dependencies: axios: 0.30.2(debug@4.4.3) - cafe-utility: 33.6.0 + cafe-utility: 33.8.0 debug: 4.4.3 isomorphic-ws: 4.0.1(ws@8.19.0) semver: 7.7.4 @@ -7539,9 +7539,10 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@solarpunkltd/file-manager-lib@1.0.5': + '@solarpunkltd/file-manager-lib@1.0.7': dependencies: - '@ethersphere/bee-js': 10.4.0 + '@ethersphere/bee-js': 11.1.1 + cafe-utility: 33.8.0 std-env: 3.10.0 transitivePeerDependencies: - bufferutil @@ -8461,7 +8462,7 @@ snapshots: bytes@3.1.2: {} - cafe-utility@33.6.0: {} + cafe-utility@33.8.0: {} call-bind-apply-helpers@1.0.2: dependencies: diff --git a/src/modules/filemanager/components/CreateDriveModal/CreateDriveModal.tsx b/src/modules/filemanager/components/CreateDriveModal/CreateDriveModal.tsx index 35ca53e..b096f20 100644 --- a/src/modules/filemanager/components/CreateDriveModal/CreateDriveModal.tsx +++ b/src/modules/filemanager/components/CreateDriveModal/CreateDriveModal.tsx @@ -62,6 +62,8 @@ export function CreateDriveModal({ const validationError = duplicate && nameExists ? 'Drive already exists. Please choose another name.' : '' useEffect(() => { + isMountedRef.current = true + return () => { isMountedRef.current = false } diff --git a/src/modules/filemanager/components/FileBrowser/FileBrowser.tsx b/src/modules/filemanager/components/FileBrowser/FileBrowser.tsx index 0d168cb..3a4079d 100644 --- a/src/modules/filemanager/components/FileBrowser/FileBrowser.tsx +++ b/src/modules/filemanager/components/FileBrowser/FileBrowser.tsx @@ -210,6 +210,7 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps) const [confirmBulkRestore, setConfirmBulkRestore] = useState(false) const [isRefreshing, setIsRefreshing] = useState(false) const [pendingCancelUpload, setPendingCancelUpload] = useState(null) + const [pendingCancelDownload, setPendingCancelDownload] = useState(null) const q = query.trim().toLowerCase() const isSearchMode = q.length > 0 @@ -371,6 +372,16 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps) setShowError, ]) + const handleDownloadClose = (uuid: string) => { + const row = downloadItems.find(i => i.uuid === uuid) + + if (row?.status === TransferStatus.Downloading) { + setPendingCancelDownload(uuid) + } else { + cancelOrDismissDownload(uuid) + } + } + const handleUploadClose = (uuid: string) => { const row = uploadItems.find(i => i.uuid === uuid) @@ -425,6 +436,18 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps) // eslint-disable-next-line react-hooks/exhaustive-deps }, [showContext, pos, contextRef]) + useEffect(() => { + isMountedRef.current = true + + return () => { + isMountedRef.current = false + + if (rafIdRef.current) { + cancelAnimationFrame(rafIdRef.current) + } + } + }, []) + useEffect(() => { let title = currentDrive?.name || '' @@ -446,16 +469,6 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps) // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSearchMode]) - useEffect(() => { - return () => { - isMountedRef.current = false - - if (rafIdRef.current) { - cancelAnimationFrame(rafIdRef.current) - } - } - }, []) - const doRefresh = async () => { handleCloseContext() @@ -610,6 +623,14 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps) } }} onCancelUploadCancel={() => setPendingCancelUpload(null)} + pendingCancelDownload={pendingCancelDownload} + onCancelDownloadConfirm={() => { + if (pendingCancelDownload) { + cancelOrDismissDownload(pendingCancelDownload) + setPendingCancelDownload(null) + } + }} + onCancelDownloadCancel={() => setPendingCancelDownload(null)} /> {isRefreshing && ( @@ -644,7 +665,7 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps) type={FileTransferType.Download} open={isDownloading} items={downloadItems} - onRowClose={(name: string) => cancelOrDismissDownload(name)} + onRowClose={handleDownloadClose} onCloseAll={() => dismissAllDownloads()} /> diff --git a/src/modules/filemanager/components/FileBrowser/FileBrowserModals.tsx b/src/modules/filemanager/components/FileBrowser/FileBrowserModals.tsx index 379c51f..c2ef69b 100644 --- a/src/modules/filemanager/components/FileBrowser/FileBrowserModals.tsx +++ b/src/modules/filemanager/components/FileBrowser/FileBrowserModals.tsx @@ -17,6 +17,7 @@ interface FileBrowserModalsProps { confirmBulkRestore: boolean showDestroyDriveModal: boolean pendingCancelUpload: string | null + pendingCancelDownload: string | null onDeleteCancel: () => void onDeleteProceed: (action: FileAction) => void onForgetConfirm: () => Promise @@ -27,6 +28,8 @@ interface FileBrowserModalsProps { onDestroyConfirm: () => Promise onCancelUploadConfirm: () => void onCancelUploadCancel: () => void + onCancelDownloadConfirm: () => void + onCancelDownloadCancel: () => void } export function FileBrowserModals({ @@ -38,6 +41,7 @@ export function FileBrowserModals({ confirmBulkRestore, showDestroyDriveModal, pendingCancelUpload, + pendingCancelDownload, onDeleteCancel, onDeleteProceed, onForgetConfirm, @@ -48,6 +52,8 @@ export function FileBrowserModals({ onDestroyConfirm, onCancelUploadConfirm, onCancelUploadCancel, + onCancelDownloadCancel, + onCancelDownloadConfirm, }: FileBrowserModalsProps): ReactElement { return ( <> @@ -124,6 +130,17 @@ export function FileBrowserModals({ onCancel={onCancelUploadCancel} /> )} + + {pendingCancelDownload && ( + + )} ) } diff --git a/src/modules/filemanager/components/FileBrowser/FileItem/FileItem.tsx b/src/modules/filemanager/components/FileBrowser/FileItem/FileItem.tsx index d43436f..be27ae0 100644 --- a/src/modules/filemanager/components/FileBrowser/FileItem/FileItem.tsx +++ b/src/modules/filemanager/components/FileBrowser/FileItem/FileItem.tsx @@ -20,7 +20,7 @@ import { DownloadProgress, FileAction, TrackDownloadProps, ViewType } from '../. import { useContextMenu } from '../../../hooks/useContextMenu' import { getUsableStamps, handleDestroyAndForgetDrive, verifyDriveSpace } from '../../../utils/bee' import { Dir, formatBytes, isTrashed, safeSetState, truncateNameMiddle } from '../../../utils/common' -import { createDownloadAbort, startDownloadingQueue } from '../../../utils/download' +import { startDownloadingQueue } from '../../../utils/download' import { FileOperation, performFileOperation } from '../../../utils/fileOperations' import { GetIconElement } from '../../../utils/GetIconElement' import type { FilePropertyGroup } from '../../../utils/infoGroups' @@ -168,15 +168,14 @@ export function FileItem({ const rawSize = latestFileInfo.customMetadata?.size const expectedSize = rawSize ? Number(rawSize) : undefined - - createDownloadAbort(latestFileInfo.name) + const uuid = uuidV4() await startDownloadingQueue( fm, - [latestFileInfo], + [{ uuid, info: latestFileInfo }], [ onDownload({ - uuid: uuidV4(), + uuid, name: latestFileInfo.name, size: formatBytes(rawSize), expectedSize, diff --git a/src/modules/filemanager/components/InitialModal/InitialModal.tsx b/src/modules/filemanager/components/InitialModal/InitialModal.tsx index 1cc9be9..dc865ea 100644 --- a/src/modules/filemanager/components/InitialModal/InitialModal.tsx +++ b/src/modules/filemanager/components/InitialModal/InitialModal.tsx @@ -89,6 +89,8 @@ export function InitialModal({ const isMountedRef = useRef(true) useEffect(() => { + isMountedRef.current = true + return () => { isMountedRef.current = false } diff --git a/src/modules/filemanager/components/UpgradeDriveModal/UpgradeDriveModal.tsx b/src/modules/filemanager/components/UpgradeDriveModal/UpgradeDriveModal.tsx index d95c69e..d0fc34f 100644 --- a/src/modules/filemanager/components/UpgradeDriveModal/UpgradeDriveModal.tsx +++ b/src/modules/filemanager/components/UpgradeDriveModal/UpgradeDriveModal.tsx @@ -159,6 +159,14 @@ export function UpgradeDriveModal({ [beeApi, walletBalance, isMountedRef, setErrorMessage, setShowError], ) + useEffect(() => { + isMountedRef.current = true + + return () => { + isMountedRef.current = false + } + }, []) + useEffect(() => { const fetchSizes = () => { const sizes = Array.from(Utils.getStampEffectiveBytesBreakpoints(false, defaultErasureCodeLevel).values()) @@ -207,12 +215,6 @@ export function UpgradeDriveModal({ setValidityEndDate(getExpiryDateByLifetime(lifetimeIndex, stamp.duration.toEndDate())) }, [lifetimeIndex, stamp.duration]) - useEffect(() => { - return () => { - isMountedRef.current = false - } - }, []) - const batchIdStr = stamp.batchID.toString() const shortBatchId = batchIdStr.length > 12 ? `${batchIdStr.slice(0, 4)}...${batchIdStr.slice(-4)}` : batchIdStr diff --git a/src/modules/filemanager/components/VersionHistoryModal/VersionList/VersionList.tsx b/src/modules/filemanager/components/VersionHistoryModal/VersionList/VersionList.tsx index 47a3413..ed4fd46 100644 --- a/src/modules/filemanager/components/VersionHistoryModal/VersionList/VersionList.tsx +++ b/src/modules/filemanager/components/VersionHistoryModal/VersionList/VersionList.tsx @@ -426,13 +426,16 @@ export function VersionsList({ versions, headFi, restoreVersion, onDownload }: V handleCloseContext() if (!fm || !beeApi) return + const rawSize = fileInfo.customMetadata?.size const expectedSize = rawSize ? Number(rawSize) : undefined const driveName = drives.find(d => d.id.toString() === fileInfo.driveId.toString())?.name ?? currentDrive?.name + const uuid = uuidV4() + await startDownloadingQueue( fm, - [fileInfo], - [onDownload({ uuid: uuidV4(), name: fileInfo.name, size: formatBytes(rawSize), expectedSize, driveName })], + [{ uuid, info: fileInfo }], + [onDownload({ uuid, name: fileInfo.name, size: formatBytes(rawSize), expectedSize, driveName })], ) }, [handleCloseContext, fm, beeApi, onDownload, drives, currentDrive], diff --git a/src/modules/filemanager/hooks/useBulkActions.ts b/src/modules/filemanager/hooks/useBulkActions.ts index a6d834a..93976f4 100644 --- a/src/modules/filemanager/hooks/useBulkActions.ts +++ b/src/modules/filemanager/hooks/useBulkActions.ts @@ -8,7 +8,7 @@ import { uuidV4 } from '../../../utils' import { DownloadProgress, TrackDownloadProps } from '../constants/transfers' import { getUsableStamps } from '../utils/bee' import { formatBytes, getFileId, safeSetState } from '../utils/common' -import { startDownloadingQueue } from '../utils/download' +import { FileInfoWithUUID, startDownloadingQueue } from '../utils/download' import { FileOperation, performBulkFileOperation } from '../utils/fileOperations' interface BulkOptions { @@ -77,23 +77,28 @@ export function useBulkActions({ listToRender, setErrorMessage, trackDownload }: async (list: FileInfo[]) => { if (!fm || !list?.length) return - const trackers: Array<(progress: DownloadProgress) => void> = [] - for (const fi of list) { + const trackers = new Array<(progress: DownloadProgress) => void>(list.length) + const infoListWitIDs: FileInfoWithUUID[] = new Array(list.length) + + for (let i = 0; i < list.length; i++) { + const fi = list[i] const rawSize = fi.customMetadata?.size as string | number | undefined const prettySize = formatBytes(rawSize) const expected = rawSize ? Number(rawSize) : undefined const driveName = drives.find(d => d.id.toString() === fi.driveId.toString())?.name - const tracker = trackDownload({ - uuid: uuidV4(), + const uuid = uuidV4() + + infoListWitIDs[i] = { uuid, info: fi } + trackers[i] = trackDownload({ + uuid, name: fi.name, size: prettySize, expectedSize: expected, driveName, }) - trackers.push(tracker) } - await startDownloadingQueue(fm, list, trackers) + await startDownloadingQueue(fm, infoListWitIDs, trackers) }, [fm, trackDownload, drives], ) diff --git a/src/modules/filemanager/hooks/useTransfers.ts b/src/modules/filemanager/hooks/useTransfers.ts index d73ebf5..4de7739 100644 --- a/src/modules/filemanager/hooks/useTransfers.ts +++ b/src/modules/filemanager/hooks/useTransfers.ts @@ -172,6 +172,7 @@ export function useTransfers({ setErrorMessage }: TransferProps) { const { fm, adminDrive, currentDrive, currentStamp, files, setShowError, refreshStamp } = useContext(FMContext) const { beeApi } = useContext(SettingsContext) const [openConflict, conflictPortal] = useUploadConflictDialog() + const isMountedRef = useRef(true) const uploadAbortsRef = useRef(new AbortManager()) const queueRef = useRef([]) @@ -406,6 +407,7 @@ export function useTransfers({ setErrorMessage }: TransferProps) { taskDrive, { ...info, onUploadProgress: progressCb }, { actHistoryAddress: task.isReplace ? task.replaceHistory : undefined }, + { signal }, ) await Promise.race([uploadPromise, checkCancellation]) @@ -827,6 +829,14 @@ export function useTransfers({ setErrorMessage }: TransferProps) { cancelledDownloadingRef.current.clear() }, []) + useEffect(() => { + isMountedRef.current = true + + return () => { + isMountedRef.current = false + } + }, []) + useEffect(() => { const handleFileUploaded = (e: Event) => { const { fileInfo } = (e as CustomEvent).detail || {} @@ -853,7 +863,6 @@ export function useTransfers({ setErrorMessage }: TransferProps) { return () => { window.removeEventListener(FILE_MANAGER_EVENTS.FILE_UPLOADED, handleFileUploaded as EventListener) - isMountedRef.current = false } }, []) diff --git a/src/modules/filemanager/utils/download.ts b/src/modules/filemanager/utils/download.ts index e2c5ba0..55642bb 100644 --- a/src/modules/filemanager/utils/download.ts +++ b/src/modules/filemanager/utils/download.ts @@ -13,12 +13,12 @@ enum Errors { SecurityError = 'SecurityError', } -export function createDownloadAbort(name: string): void { - downloadAborts.create(name) +export function createDownloadAbort(id: string): void { + downloadAborts.create(id) } -export function abortDownload(name: string): void { - downloadAborts.abort(name) +export function abortDownload(id: string): void { + downloadAborts.abort(id) } const processStream = async ( @@ -130,8 +130,13 @@ const streamToBlob = async ( return new Blob([combined], { type: mimeType }) } -interface FileInfoWithHandle { +export interface FileInfoWithUUID { + uuid: string info: FileInfo +} + +interface FileInfoWithHandle { + infoWithId: FileInfoWithUUID handle?: FileSystemFileHandle cancelled?: boolean } @@ -149,17 +154,17 @@ const isUserCancellation = (error: unknown): boolean => { } const getSingleFileHandle = async ( - info: FileInfo, + infoWithId: FileInfoWithUUID, defaultDownloadFolder: string, ): Promise => { - const { mime, ext } = guessMime(info.name, info.customMetadata) + const { mime, ext } = guessMime(infoWithId.info.name, infoWithId.info.customMetadata) const pickerOptions: { suggestedName: string startIn: string types?: Array<{ accept: Record }> } = { - suggestedName: info.name, + suggestedName: infoWithId.info.name, startIn: defaultDownloadFolder, } @@ -171,20 +176,20 @@ const getSingleFileHandle = async ( // eslint-disable-next-line @typescript-eslint/no-explicit-any const handle = (await (window as any).showSaveFilePicker(pickerOptions)) as FileSystemFileHandle - return [{ info, handle }] + return [{ infoWithId, handle }] } catch (error: unknown) { - return isUserCancellation(error) ? [{ info, cancelled: true }] : undefined + return isUserCancellation(error) ? [{ infoWithId, cancelled: true }] : undefined } } const getMultipleFileHandles = async ( - infoList: FileInfo[], + infoWithIdList: FileInfoWithUUID[], defaultDownloadFolder: string, ): Promise => { if (!isDirectoryPickerSupported()) { const handles: FileInfoWithHandle[] = [] - for (const info of infoList) { + for (const info of infoWithIdList) { const result = await getSingleFileHandle(info, defaultDownloadFolder) if (!result) return undefined @@ -203,37 +208,37 @@ const getMultipleFileHandles = async ( const handles: FileInfoWithHandle[] = [] - for (const info of infoList) { + for (const infoWithId of infoWithIdList) { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fileHandle = (await (dirHandle as any).getFileHandle(info.name, { + const fileHandle = (await (dirHandle as any).getFileHandle(infoWithId.info.name, { create: true, })) as FileSystemFileHandle - handles.push({ info, handle: fileHandle }) + handles.push({ infoWithId, handle: fileHandle }) } catch (error: unknown) { // eslint-disable-next-line no-console - console.error(`Failed to create file handle for ${info.name}:`, error) - handles.push({ info, cancelled: true }) + console.error(`Failed to create file handle for ${infoWithId.info.name}:`, error) + handles.push({ infoWithId, cancelled: true }) } } return handles } catch (error: unknown) { - return isUserCancellation(error) ? infoList.map(info => ({ info, cancelled: true })) : undefined + return isUserCancellation(error) ? infoWithIdList.map(infoWithId => ({ infoWithId, cancelled: true })) : undefined } } -const getFileHandles = (infoList: FileInfo[]): Promise => { +const getFileHandles = (infoWithIdList: FileInfoWithUUID[]): Promise => { const defaultDownloadFolder = 'downloads' - if (!isPickerSupported()) return Promise.resolve(infoList.map(info => ({ info }))) + if (!isPickerSupported()) return Promise.resolve(infoWithIdList.map(infoWithId => ({ infoWithId }))) - if (infoList.length === 1) { - return getSingleFileHandle(infoList[0], defaultDownloadFolder) + if (infoWithIdList.length === 1) { + return getSingleFileHandle(infoWithIdList[0], defaultDownloadFolder) } - return getMultipleFileHandles(infoList, defaultDownloadFolder) + return getMultipleFileHandles(infoWithIdList, defaultDownloadFolder) } const downloadToDisk = async ( @@ -324,26 +329,26 @@ const downloadFromUrl = (url: string, fileName: string): void => { export const startDownloadingQueue = async ( fm: FileManager, - infoList: FileInfo[], + infoListWithIds: FileInfoWithUUID[], trackers?: Array<(progress: DownloadProgress) => void>, isOpenWindow?: boolean, ): Promise => { - if (!infoList.length || (trackers && trackers.length !== infoList.length)) return + if (!infoListWithIds.length || (trackers && trackers.length !== infoListWithIds.length)) return try { const fileHandles: FileInfoWithHandle[] | undefined = isOpenWindow - ? infoList.map(info => ({ info })) - : await getFileHandles(infoList) + ? infoListWithIds.map(infoWithId => ({ infoWithId })) + : await getFileHandles(infoListWithIds) if (!fileHandles) return await Promise.all( fileHandles.map(async (fh, i) => { - const name = fh.info.name const tracker = trackers ? trackers[i] : undefined - createDownloadAbort(name) - const signal = downloadAborts.getSignal(name) + const uuid = fh.infoWithId.uuid + createDownloadAbort(uuid) + const signal = downloadAborts.getSignal(uuid) try { if (fh.cancelled) { @@ -352,11 +357,13 @@ export const startDownloadingQueue = async ( return } - const dataStreams = (await fm.download(fh.info)) as ReadableStream[] + const dataStreams = (await fm.download(fh.infoWithId.info, undefined, undefined, { + signal, + })) as ReadableStream[] if (!dataStreams || dataStreams.length === 0) { // eslint-disable-next-line no-console - console.error(`No data streams returned for ${name}`) + console.error(`No data streams returned for ${fh.infoWithId.info.name}`) tracker?.({ progress: 0, isDownloading: false, state: DownloadState.Error }) return @@ -365,7 +372,7 @@ export const startDownloadingQueue = async ( let success = false if (isOpenWindow || !fh.handle) { - success = await downloadToBlob(dataStreams, fh.info, tracker, isOpenWindow, signal) + success = await downloadToBlob(dataStreams, fh.infoWithId.info, tracker, isOpenWindow, signal) } else { success = await downloadToDisk(dataStreams, fh.handle, tracker, signal) } @@ -375,7 +382,7 @@ export const startDownloadingQueue = async ( } if (success) { - const size = fh.info.customMetadata?.size + const size = fh.infoWithId.info.customMetadata?.size const finalProgress = size ? Number(size) : 0 tracker({ progress: finalProgress, isDownloading: false }) @@ -397,7 +404,7 @@ export const startDownloadingQueue = async ( tracker?.({ progress: 0, isDownloading: false, state: DownloadState.Cancelled }) } } finally { - downloadAborts.abort(name) + downloadAborts.abort(uuid) } }), ) diff --git a/vite.config.mts b/vite.config.mts index 86a479e..84ef7e9 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -74,6 +74,7 @@ export default defineConfig(({ mode }) => { extensions: ['.ts', '.tsx', '.js', '.jsx', '.css', '.scss'], }, optimizeDeps: { + // include: [], // exclude: [], // add libs for local development, if needed, e.g.: @solarpunkltd/file-manager-lib }, build: {