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:
Bálint Ujvári
2026-03-05 16:55:09 +01:00
parent e00918b192
commit 308ec3dcc0
13 changed files with 154 additions and 85 deletions
+42 -35
View File
@@ -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)
}
}),
)