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
This commit is contained in:
+2
-2
@@ -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",
|
||||
|
||||
Generated
+17
-16
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -210,6 +210,7 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
const [confirmBulkRestore, setConfirmBulkRestore] = useState(false)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [pendingCancelUpload, setPendingCancelUpload] = useState<string | null>(null)
|
||||
const [pendingCancelDownload, setPendingCancelDownload] = useState<string | null>(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()}
|
||||
/>
|
||||
<NotificationBar setErrorMessage={setErrorMessage} />
|
||||
|
||||
@@ -17,6 +17,7 @@ interface FileBrowserModalsProps {
|
||||
confirmBulkRestore: boolean
|
||||
showDestroyDriveModal: boolean
|
||||
pendingCancelUpload: string | null
|
||||
pendingCancelDownload: string | null
|
||||
onDeleteCancel: () => void
|
||||
onDeleteProceed: (action: FileAction) => void
|
||||
onForgetConfirm: () => Promise<void>
|
||||
@@ -27,6 +28,8 @@ interface FileBrowserModalsProps {
|
||||
onDestroyConfirm: () => Promise<void>
|
||||
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 && (
|
||||
<ConfirmModal
|
||||
title="Cancel download?"
|
||||
message="Are you sure you want to cancel this download?"
|
||||
confirmLabel="Cancel download"
|
||||
cancelLabel="Keep downloading"
|
||||
onConfirm={onCancelDownloadConfirm}
|
||||
onCancel={onCancelDownloadCancel}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -89,6 +89,8 @@ export function InitialModal({
|
||||
const isMountedRef = useRef(true)
|
||||
|
||||
useEffect(() => {
|
||||
isMountedRef.current = true
|
||||
|
||||
return () => {
|
||||
isMountedRef.current = false
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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<FileInfoWithUUID>(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],
|
||||
)
|
||||
|
||||
@@ -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<AbortManager>(new AbortManager())
|
||||
const queueRef = useRef<UploadTask[]>([])
|
||||
@@ -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
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -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<FileInfoWithHandle[] | undefined> => {
|
||||
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<string, string[]> }>
|
||||
} = {
|
||||
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<FileInfoWithHandle[] | undefined> => {
|
||||
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<FileInfoWithHandle[] | undefined> => {
|
||||
const getFileHandles = (infoWithIdList: FileInfoWithUUID[]): Promise<FileInfoWithHandle[] | undefined> => {
|
||||
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<void> => {
|
||||
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<Uint8Array>[]
|
||||
const dataStreams = (await fm.download(fh.infoWithId.info, undefined, undefined, {
|
||||
signal,
|
||||
})) as ReadableStream<Uint8Array>[]
|
||||
|
||||
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)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user