Fix: file-manager and swarm-desktop bugs (#714)
- drive capacity display with stamp polling - download/upload progress handling - overlay and tooltip issues - FileMaganger readme - ultra-light mode handling - account feed view page - download media files - remove not found syncing link - fix ultra light node wallet page - tooltip issues --------- Co-authored-by: Andrei Mitrea <andrei.mitrea.hq@gmail.com> Co-authored-by: nidishk <nidishkrishnan45@gmail.com> Co-authored-by: Ferenc Sárai <sarai.ferenc@gmail.com> Co-authored-by: Nándor Komlódi <nandor.komlodi@gmail.com> Co-authored-by: rolandlor <33499567+rolandlor@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useState, useContext, useRef, useEffect } from 'react'
|
||||
import { Context as FMContext } from '../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||
import type { FileInfo, FileInfoOptions, UploadProgress } from '@solarpunkltd/file-manager-lib'
|
||||
import { ConflictAction, useUploadConflictDialog } from './useUploadConflictDialog'
|
||||
import { formatBytes, safeSetState } from '../utils/common'
|
||||
import { formatBytes, safeSetState, truncateNameMiddle } from '../utils/common'
|
||||
import {
|
||||
DownloadProgress,
|
||||
DownloadState,
|
||||
@@ -10,13 +11,17 @@ import {
|
||||
TrackDownloadProps,
|
||||
TransferStatus,
|
||||
} from '../constants/transfers'
|
||||
import { calculateStampCapacityMetrics } from '../utils/bee'
|
||||
import { validateStampStillExists, verifyDriveSpace } from '../utils/bee'
|
||||
import { isTrashed } from '../utils/common'
|
||||
import { abortDownload } from '../utils/download'
|
||||
import { AbortManager } from '../utils/abortManager'
|
||||
import { uuidV4 } from '../../../utils'
|
||||
import { FILE_MANAGER_EVENTS } from '../constants/common'
|
||||
|
||||
const SAMPLE_WINDOW_MS = 500
|
||||
const ETA_SMOOTHING = 0.3
|
||||
const MAX_UPLOAD_FILES = 10
|
||||
const ABORT_EVENT = 'abort'
|
||||
|
||||
type ResolveResult = {
|
||||
cancelled: boolean
|
||||
@@ -27,6 +32,7 @@ type ResolveResult = {
|
||||
}
|
||||
|
||||
type TransferItem = {
|
||||
uuid: string
|
||||
name: string
|
||||
size?: string
|
||||
percent: number
|
||||
@@ -47,12 +53,15 @@ type ETAState = {
|
||||
type UploadMeta = Record<string, string | number>
|
||||
|
||||
type UploadTask = {
|
||||
uuid: string
|
||||
file: File
|
||||
finalName: string
|
||||
prettySize?: string
|
||||
isReplace: boolean
|
||||
replaceTopic?: string
|
||||
replaceHistory?: string
|
||||
driveId: string
|
||||
driveName: string
|
||||
}
|
||||
|
||||
const normalizeCustomMetadata = (meta: UploadMeta): Record<string, string> => {
|
||||
@@ -62,15 +71,21 @@ const normalizeCustomMetadata = (meta: UploadMeta): Record<string, string> => {
|
||||
return out
|
||||
}
|
||||
|
||||
const buildUploadMeta = (files: File[] | FileList, path?: string): UploadMeta => {
|
||||
const buildUploadMeta = (files: File[] | FileList, path?: string, existingFile?: FileInfo): UploadMeta => {
|
||||
const arr = Array.from(files as File[])
|
||||
const totalSize = arr.reduce((acc, f) => acc + (f.size || 0), 0)
|
||||
const primary = arr[0]
|
||||
|
||||
const previousAccumulated = existingFile
|
||||
? Number(existingFile.customMetadata?.accumulatedSize || existingFile.customMetadata?.size || 0)
|
||||
: 0
|
||||
const accumulatedSize = previousAccumulated + totalSize
|
||||
|
||||
const meta: UploadMeta = {
|
||||
size: String(totalSize),
|
||||
fileCount: String(arr.length),
|
||||
mime: primary?.type || 'application/octet-stream',
|
||||
accumulatedSize: String(accumulatedSize),
|
||||
}
|
||||
|
||||
if (path) meta.path = path
|
||||
@@ -120,17 +135,23 @@ const calculateETA = (
|
||||
}
|
||||
}
|
||||
|
||||
const updateTransferItems = <T extends TransferItem>(items: T[], name: string, update: Partial<T>): T[] => {
|
||||
return items.map(item => (item.name === name ? { ...item, ...update } : item))
|
||||
const updateTransferItems = <T extends TransferItem>(items: T[], uuid: string, update: Partial<T>): T[] => {
|
||||
return items.map(item => {
|
||||
const matches = item.uuid === uuid
|
||||
|
||||
return matches ? { ...item, ...update } : item
|
||||
})
|
||||
}
|
||||
|
||||
const createTransferItem = (
|
||||
uuid: string,
|
||||
name: string,
|
||||
size: string | undefined,
|
||||
kind: FileTransferType,
|
||||
driveName?: string,
|
||||
status: TransferStatus = TransferStatus.Uploading,
|
||||
): TransferItem => ({
|
||||
uuid,
|
||||
name,
|
||||
size,
|
||||
percent: 0,
|
||||
@@ -147,13 +168,14 @@ interface TransferProps {
|
||||
}
|
||||
|
||||
export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
const { fm, currentDrive, currentStamp, files, setShowError, refreshStamp } = useContext(FMContext)
|
||||
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[]>([])
|
||||
const runningRef = useRef(false)
|
||||
const cancelledNamesRef = useRef<Set<string>>(new Set())
|
||||
const cancelledQueuedRef = useRef<Set<string>>(new Set())
|
||||
const cancelledUploadingRef = useRef<Set<string>>(new Set())
|
||||
const cancelledDownloadingRef = useRef<Set<string>>(new Set())
|
||||
|
||||
@@ -167,23 +189,27 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
i => i.status !== TransferStatus.Done && i.status !== TransferStatus.Error && i.status !== TransferStatus.Cancelled,
|
||||
)
|
||||
|
||||
const clearAllFlagsFor = useCallback((name: string) => {
|
||||
cancelledNamesRef.current.delete(name)
|
||||
cancelledUploadingRef.current.delete(name)
|
||||
uploadAbortsRef.current.abort(name)
|
||||
queueRef.current = queueRef.current.filter(t => t.finalName !== name)
|
||||
const clearAllFlagsFor = useCallback((uuid: string) => {
|
||||
cancelledQueuedRef.current.delete(uuid)
|
||||
cancelledUploadingRef.current.delete(uuid)
|
||||
uploadAbortsRef.current.abort(uuid)
|
||||
queueRef.current = queueRef.current.filter(t => {
|
||||
return t.uuid !== uuid
|
||||
})
|
||||
}, [])
|
||||
|
||||
const ensureQueuedRow = useCallback(
|
||||
(name: string, kind: FileTransferType, size?: string, driveName?: string) => {
|
||||
clearAllFlagsFor(name)
|
||||
|
||||
(uuid: string, name: string, kind: FileTransferType, size?: string, driveName?: string) => {
|
||||
safeSetState(
|
||||
isMountedRef,
|
||||
setUploadItems,
|
||||
)(prev => {
|
||||
const idx = prev.findIndex(p => p.name === name)
|
||||
const base = createTransferItem(name, size, kind, driveName, TransferStatus.Queued)
|
||||
const idx = prev.findIndex(p => p.uuid === uuid && p.status !== TransferStatus.Done)
|
||||
const base = createTransferItem(uuid, name, size, kind, driveName, TransferStatus.Queued)
|
||||
|
||||
if (idx !== -1) {
|
||||
clearAllFlagsFor(uuid)
|
||||
}
|
||||
|
||||
if (idx === -1) return [...prev, base]
|
||||
const copy = [...prev]
|
||||
@@ -211,6 +237,10 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
const existing = taken[0]
|
||||
const isTrashedExisting = existing ? isTrashed(existing) : false
|
||||
|
||||
if (!existing && allTakenNames.has(originalName)) {
|
||||
return { cancelled: false, finalName: originalName, isReplace: false }
|
||||
}
|
||||
|
||||
const choice = await openConflict({
|
||||
originalName,
|
||||
existingNames: allTakenNames,
|
||||
@@ -237,14 +267,19 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
)
|
||||
|
||||
const trackUpload = useCallback(
|
||||
(name: string, size?: string, kind: FileTransferType = FileTransferType.Upload) => {
|
||||
(
|
||||
uuid: string,
|
||||
name: string,
|
||||
size?: string,
|
||||
kind: FileTransferType = FileTransferType.Upload,
|
||||
driveName?: string,
|
||||
) => {
|
||||
if (!isMountedRef.current) {
|
||||
return () => {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
const driveName = currentDrive?.name
|
||||
const startedAt = Date.now()
|
||||
|
||||
let etaState: ETAState = {
|
||||
@@ -254,8 +289,11 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
}
|
||||
|
||||
setUploadItems(prev => {
|
||||
const idx = prev.findIndex(p => p.name === name)
|
||||
const base = createTransferItem(name, size, kind, driveName, TransferStatus.Uploading)
|
||||
const existing = prev.find(p => p.uuid === uuid)
|
||||
const actualDriveName = existing?.driveName || driveName
|
||||
|
||||
const idx = prev.findIndex(p => p.uuid === uuid)
|
||||
const base = createTransferItem(uuid, name, size, kind, actualDriveName, TransferStatus.Uploading)
|
||||
|
||||
if (idx === -1) return [...prev, base]
|
||||
const copy = [...prev]
|
||||
@@ -265,7 +303,9 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
})
|
||||
|
||||
const onProgress = (progress: UploadProgress) => {
|
||||
if (cancelledUploadingRef.current.has(name) || !isMountedRef.current) return
|
||||
const signal = uploadAbortsRef.current.getSignal(uuid)
|
||||
|
||||
if (cancelledUploadingRef.current.has(uuid) || !isMountedRef.current || signal?.aborted) return
|
||||
|
||||
if (progress.total > 0) {
|
||||
const now = Date.now()
|
||||
@@ -274,91 +314,134 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
const { etaSec, updatedState } = calculateETA(etaState, progress, startedAt, now)
|
||||
etaState = updatedState
|
||||
|
||||
setUploadItems(prev =>
|
||||
updateTransferItems(prev, name, {
|
||||
percent: Math.max(prev.find(it => it.name === name)?.percent || 0, chunkPercentage),
|
||||
kind,
|
||||
etaSec,
|
||||
}),
|
||||
)
|
||||
const isComplete = progress.processed >= progress.total
|
||||
|
||||
if (progress.processed >= progress.total) {
|
||||
const finishedAt = Date.now()
|
||||
setUploadItems(prev =>
|
||||
updateTransferItems(prev, name, {
|
||||
setUploadItems(prev => {
|
||||
const existing = prev.find(it => it.uuid === uuid && it.status === TransferStatus.Uploading)
|
||||
|
||||
if (!existing || existing.status === TransferStatus.Done) return prev
|
||||
|
||||
if (isComplete) {
|
||||
return updateTransferItems(prev, uuid, {
|
||||
percent: 100,
|
||||
status: TransferStatus.Done,
|
||||
kind,
|
||||
etaSec: 0,
|
||||
elapsedSec: Math.round((finishedAt - startedAt) / 1000),
|
||||
}),
|
||||
)
|
||||
}
|
||||
elapsedSec: Math.round((now - startedAt) / 1000),
|
||||
})
|
||||
}
|
||||
|
||||
return updateTransferItems(prev, uuid, {
|
||||
percent: Math.max(existing.percent, chunkPercentage),
|
||||
kind,
|
||||
etaSec,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return onProgress
|
||||
},
|
||||
[currentDrive?.name],
|
||||
[],
|
||||
)
|
||||
|
||||
const processUploadTask = useCallback(
|
||||
async (task: UploadTask) => {
|
||||
if (!fm || !currentDrive) return
|
||||
if (!fm) return
|
||||
|
||||
const taskDrive = fm.driveList.find(d => d.id.toString() === task.driveId)
|
||||
|
||||
if (!taskDrive) {
|
||||
return
|
||||
}
|
||||
|
||||
const existingFile = task.isReplace ? files.find(f => f.topic.toString() === task.replaceTopic) : undefined
|
||||
|
||||
const info: FileInfoOptions = {
|
||||
name: task.finalName,
|
||||
files: [task.file],
|
||||
customMetadata: normalizeCustomMetadata(buildUploadMeta([task.file])),
|
||||
customMetadata: normalizeCustomMetadata(buildUploadMeta([task.file], undefined, existingFile)),
|
||||
topic: task.isReplace ? task.replaceTopic : undefined,
|
||||
}
|
||||
|
||||
const progressCb = trackUpload(
|
||||
task.uuid,
|
||||
task.finalName,
|
||||
task.prettySize,
|
||||
task.isReplace ? FileTransferType.Update : FileTransferType.Upload,
|
||||
taskDrive.name,
|
||||
)
|
||||
|
||||
safeSetState(
|
||||
isMountedRef,
|
||||
setUploadItems,
|
||||
)(prev =>
|
||||
updateTransferItems(prev, task.finalName, {
|
||||
updateTransferItems(prev, task.uuid, {
|
||||
status: TransferStatus.Uploading,
|
||||
kind: task.isReplace ? FileTransferType.Update : FileTransferType.Upload,
|
||||
startedAt: Date.now(),
|
||||
}),
|
||||
)
|
||||
|
||||
uploadAbortsRef.current.create(task.finalName)
|
||||
uploadAbortsRef.current.create(task.uuid)
|
||||
const signal = uploadAbortsRef.current.getSignal(task.uuid)
|
||||
|
||||
let reject: (reason?: Error) => void
|
||||
const abortHandler = () => {
|
||||
reject(new Error('Upload cancelled'))
|
||||
}
|
||||
|
||||
const checkCancellation = new Promise<never>((_, rej) => {
|
||||
reject = rej
|
||||
signal?.addEventListener(ABORT_EVENT, abortHandler)
|
||||
})
|
||||
|
||||
try {
|
||||
await fm.upload(
|
||||
currentDrive,
|
||||
if (signal?.aborted) {
|
||||
throw new Error('Upload cancelled')
|
||||
}
|
||||
|
||||
const uploadPromise = fm.upload(
|
||||
taskDrive,
|
||||
{ ...info, onUploadProgress: progressCb },
|
||||
{ actHistoryAddress: task.isReplace ? task.replaceHistory : undefined },
|
||||
)
|
||||
|
||||
await Promise.race([uploadPromise, checkCancellation])
|
||||
|
||||
if (currentStamp) {
|
||||
await refreshStamp(currentStamp.batchID.toString())
|
||||
}
|
||||
} catch {
|
||||
const wasCancelled = cancelledUploadingRef.current.has(task.finalName)
|
||||
} catch (error) {
|
||||
const wasCancelled = cancelledUploadingRef.current.has(task.uuid) || signal?.aborted
|
||||
|
||||
if (!wasCancelled) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
setErrorMessage?.(`Upload failed: ${errorMsg}`)
|
||||
setShowError(true)
|
||||
}
|
||||
|
||||
safeSetState(
|
||||
isMountedRef,
|
||||
setUploadItems,
|
||||
)(prev =>
|
||||
updateTransferItems(prev, task.finalName, {
|
||||
updateTransferItems(prev, task.uuid, {
|
||||
status: wasCancelled ? TransferStatus.Cancelled : TransferStatus.Error,
|
||||
}),
|
||||
)
|
||||
} finally {
|
||||
uploadAbortsRef.current.abort(task.finalName)
|
||||
cancelledUploadingRef.current.delete(task.finalName)
|
||||
cancelledNamesRef.current.delete(task.finalName)
|
||||
signal?.removeEventListener(ABORT_EVENT, abortHandler)
|
||||
|
||||
const wasCancelled = cancelledUploadingRef.current.has(task.uuid) || signal?.aborted
|
||||
|
||||
if (!wasCancelled) {
|
||||
uploadAbortsRef.current.abort(task.uuid)
|
||||
cancelledUploadingRef.current.delete(task.uuid)
|
||||
cancelledQueuedRef.current.delete(task.uuid)
|
||||
}
|
||||
}
|
||||
},
|
||||
[fm, currentDrive, currentStamp, trackUpload, refreshStamp],
|
||||
[fm, files, currentStamp, trackUpload, refreshStamp, setShowError, setErrorMessage],
|
||||
)
|
||||
|
||||
const trackDownload = useCallback(
|
||||
@@ -369,7 +452,7 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const driveName = currentDrive?.name
|
||||
const driveName = props.driveName ?? currentDrive?.name
|
||||
|
||||
let startedAt: number | undefined
|
||||
let etaState: ETAState = {
|
||||
@@ -380,14 +463,15 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
|
||||
setDownloadItems(prev => {
|
||||
const row = createTransferItem(
|
||||
props.uuid,
|
||||
props.name,
|
||||
props.size,
|
||||
FileTransferType.Download,
|
||||
driveName,
|
||||
TransferStatus.Downloading,
|
||||
)
|
||||
row.startedAt = undefined // Downloads start timing when first progress is received
|
||||
const idx = prev.findIndex(p => p.name === props.name)
|
||||
row.startedAt = undefined
|
||||
const idx = prev.findIndex(p => p.uuid === props.uuid)
|
||||
|
||||
if (idx === -1) return [...prev, row]
|
||||
const out = [...prev]
|
||||
@@ -417,10 +501,10 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
}
|
||||
|
||||
setDownloadItems(prev =>
|
||||
updateTransferItems(prev, props.name, {
|
||||
percent: Math.max(prev.find(it => it.name === props.name)?.percent || 0, percent),
|
||||
updateTransferItems(prev, props.uuid, {
|
||||
percent: Math.max(prev.find(it => it.uuid === props.uuid)?.percent || 0, percent),
|
||||
etaSec,
|
||||
startedAt: prev.find(it => it.name === props.name)?.startedAt ?? startedAt,
|
||||
startedAt: prev.find(it => it.uuid === props.uuid)?.startedAt ?? startedAt,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -428,16 +512,16 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
const finishedAt = Date.now()
|
||||
|
||||
setDownloadItems(prev => {
|
||||
const currentItem = prev.find(it => it.name === props.name)
|
||||
const currentItem = prev.find(it => it.uuid === props.uuid)
|
||||
const elapsedSec = currentItem?.startedAt ? Math.round((finishedAt - currentItem.startedAt) / 1000) : 0
|
||||
|
||||
if (dp.state === DownloadState.Cancelled || dp.state === DownloadState.Error) {
|
||||
const wasCancelled =
|
||||
dp.state === DownloadState.Cancelled || cancelledDownloadingRef.current.has(props.name)
|
||||
dp.state === DownloadState.Cancelled || cancelledDownloadingRef.current.has(props.uuid)
|
||||
|
||||
cancelledDownloadingRef.current.delete(props.name)
|
||||
cancelledDownloadingRef.current.delete(props.uuid)
|
||||
|
||||
return updateTransferItems(prev, props.name, {
|
||||
return updateTransferItems(prev, props.uuid, {
|
||||
status: wasCancelled ? TransferStatus.Cancelled : TransferStatus.Error,
|
||||
etaSec: undefined,
|
||||
elapsedSec: 0,
|
||||
@@ -445,7 +529,7 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
})
|
||||
}
|
||||
|
||||
return updateTransferItems(prev, props.name, {
|
||||
return updateTransferItems(prev, props.uuid, {
|
||||
percent: 100,
|
||||
status: TransferStatus.Done,
|
||||
etaSec: 0,
|
||||
@@ -457,23 +541,48 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
|
||||
return onProgress
|
||||
},
|
||||
[currentDrive?.name],
|
||||
// currentDrive casues rerenders and flickering during the progress tracking
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
)
|
||||
|
||||
const uploadFiles = useCallback(
|
||||
(picked: FileList | File[]): void => {
|
||||
const filesArr = Array.from(picked)
|
||||
|
||||
if (filesArr.length === 0 || !fm || !currentDrive) return
|
||||
if (filesArr.length === 0 || !fm || !currentDrive || !currentStamp) return
|
||||
|
||||
const currentlyQueued = queueRef.current.length
|
||||
const newFilesCount = filesArr.length
|
||||
const totalAfterAdd = currentlyQueued + newFilesCount
|
||||
|
||||
if (totalAfterAdd > MAX_UPLOAD_FILES) {
|
||||
setErrorMessage?.(
|
||||
`You’re trying to upload ${totalAfterAdd} files, but the limit is ${MAX_UPLOAD_FILES}. Please upload fewer files.`,
|
||||
)
|
||||
setShowError(true)
|
||||
|
||||
return
|
||||
}
|
||||
// TODO: move out this function from the cb and use as a util for better readaility
|
||||
const preflight = async (): Promise<UploadTask[]> => {
|
||||
const progressNames = new Set<string>(uploadItems.map(u => u.name))
|
||||
const progressNames = new Set<string>(
|
||||
uploadItems.filter(u => u.driveName === currentDrive.name).map(u => u.name),
|
||||
)
|
||||
const sameDrive = collectSameDrive(currentDrive.id.toString())
|
||||
const onDiskNames = new Set<string>(sameDrive.map((fi: FileInfo) => fi.name))
|
||||
const reserved = new Set<string>()
|
||||
const tasks: UploadTask[] = []
|
||||
|
||||
let remainingBytes = calculateStampCapacityMetrics(currentStamp || null, currentDrive).remainingBytes
|
||||
const allTaken = new Set<string>([
|
||||
...Array.from(onDiskNames),
|
||||
...Array.from(reserved),
|
||||
...Array.from(progressNames),
|
||||
])
|
||||
|
||||
// Track cumulative file sizes for capacity verification
|
||||
let fileSizeSum = 0
|
||||
let fileCount = 0
|
||||
|
||||
const processFile = async (file: File): Promise<UploadTask | null> => {
|
||||
if (!currentStamp || !currentStamp.usable) {
|
||||
@@ -483,28 +592,30 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
const uuid = uuidV4()
|
||||
|
||||
const meta = buildUploadMeta([file])
|
||||
const prettySize = formatBytes(meta.size)
|
||||
|
||||
const allTaken = new Set<string>([
|
||||
...Array.from(onDiskNames),
|
||||
...Array.from(reserved),
|
||||
...Array.from(progressNames),
|
||||
])
|
||||
fileSizeSum += file.size
|
||||
fileCount += 1
|
||||
|
||||
if (file.size > remainingBytes) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'Skipping upload - insufficient space:',
|
||||
file.name,
|
||||
'size:',
|
||||
file.size,
|
||||
'remaining:',
|
||||
remainingBytes,
|
||||
)
|
||||
setErrorMessage?.('There is not enough space to upload: ' + file.name)
|
||||
setShowError(true)
|
||||
const { ok } = verifyDriveSpace({
|
||||
fm,
|
||||
redundancyLevel: currentDrive.redundancyLevel,
|
||||
stamp: currentStamp,
|
||||
useInfoSize: true,
|
||||
driveId: currentDrive.id.toString(),
|
||||
adminRedundancy: adminDrive?.redundancyLevel,
|
||||
fileSize: fileSizeSum,
|
||||
fileCount,
|
||||
cb: err => {
|
||||
setErrorMessage?.(err + ' (' + truncateNameMiddle(file.name) + ')')
|
||||
setShowError(true)
|
||||
},
|
||||
})
|
||||
|
||||
if (!ok) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -533,9 +644,9 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
|
||||
if (!retryInvalidCombo && !retryInvalidName) {
|
||||
reserved.add(finalName)
|
||||
remainingBytes -= file.size
|
||||
|
||||
ensureQueuedRow(
|
||||
uuid,
|
||||
finalName,
|
||||
isReplace ? FileTransferType.Update : FileTransferType.Upload,
|
||||
prettySize,
|
||||
@@ -543,12 +654,15 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
)
|
||||
|
||||
return {
|
||||
uuid,
|
||||
file,
|
||||
finalName,
|
||||
prettySize,
|
||||
isReplace: Boolean(isReplace),
|
||||
replaceTopic,
|
||||
replaceHistory,
|
||||
driveId: currentDrive.id.toString(),
|
||||
driveName: currentDrive.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,7 +675,8 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
|
||||
if (task) {
|
||||
tasks.push(task)
|
||||
} else if (file.size > remainingBytes) {
|
||||
} else {
|
||||
// Stop processing remaining files if capacity check failed
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -579,14 +694,14 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
|
||||
if (!task) break
|
||||
|
||||
const isCancelled = cancelledNamesRef.current.has(task.finalName)
|
||||
const isCancelled = cancelledQueuedRef.current.has(task.uuid)
|
||||
|
||||
if (isCancelled) {
|
||||
safeSetState(
|
||||
isMountedRef,
|
||||
setUploadItems,
|
||||
)(prev => updateTransferItems(prev, task.finalName, { status: TransferStatus.Cancelled }))
|
||||
cancelledNamesRef.current.delete(task.finalName)
|
||||
)(prev => updateTransferItems(prev, task.uuid, { status: TransferStatus.Cancelled }))
|
||||
cancelledQueuedRef.current.delete(task.uuid)
|
||||
queueRef.current.shift()
|
||||
} else {
|
||||
await processUploadTask(task)
|
||||
@@ -595,10 +710,34 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
}
|
||||
} finally {
|
||||
runningRef.current = false
|
||||
|
||||
if (queueRef.current.length > 0) {
|
||||
runQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
if (!currentStamp || !currentStamp.usable) {
|
||||
setErrorMessage?.('Stamp is not usable.')
|
||||
setShowError(true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (beeApi) {
|
||||
const stampValid = await validateStampStillExists(beeApi, currentStamp.batchID)
|
||||
|
||||
if (!stampValid) {
|
||||
setErrorMessage?.(
|
||||
'The selected stamp is no longer valid or has been deleted. Please select a different stamp.',
|
||||
)
|
||||
setShowError(true)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const tasks = await preflight()
|
||||
queueRef.current = queueRef.current.concat(tasks)
|
||||
runQueue()
|
||||
@@ -613,69 +752,73 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
ensureQueuedRow,
|
||||
processUploadTask,
|
||||
uploadItems,
|
||||
adminDrive,
|
||||
setShowError,
|
||||
setErrorMessage,
|
||||
beeApi,
|
||||
],
|
||||
)
|
||||
|
||||
const cancelOrDismissUpload = useCallback(
|
||||
(name: string) => {
|
||||
(uuid: string) => {
|
||||
safeSetState(
|
||||
isMountedRef,
|
||||
setUploadItems,
|
||||
)(prev => {
|
||||
const row = prev.find(r => r.name === name)
|
||||
const row = prev.find(r => r.uuid === uuid)
|
||||
|
||||
if (!row) return prev
|
||||
|
||||
if (row.status === TransferStatus.Queued) {
|
||||
cancelledNamesRef.current.add(name)
|
||||
queueRef.current = queueRef.current.filter(t => t.finalName !== name)
|
||||
cancelledQueuedRef.current.add(row.uuid)
|
||||
queueRef.current = queueRef.current.filter(t => t.uuid !== row.uuid)
|
||||
|
||||
return prev.map(r => (r.name === name ? { ...r, status: TransferStatus.Cancelled } : r))
|
||||
return prev.map(r => (r.uuid === row.uuid ? { ...r, status: TransferStatus.Cancelled } : r))
|
||||
}
|
||||
|
||||
if (row.status === TransferStatus.Uploading) {
|
||||
cancelledUploadingRef.current.add(name)
|
||||
uploadAbortsRef.current.abort(name)
|
||||
cancelledUploadingRef.current.add(row.uuid)
|
||||
uploadAbortsRef.current.abort(row.uuid)
|
||||
|
||||
return prev.map(r => (r.name === name ? { ...r, status: TransferStatus.Cancelled } : r))
|
||||
return prev.map(r => (r.uuid === uuid ? { ...r, status: TransferStatus.Cancelled } : r))
|
||||
}
|
||||
|
||||
clearAllFlagsFor(name)
|
||||
clearAllFlagsFor(row.uuid)
|
||||
|
||||
return prev.filter(r => r.name !== name)
|
||||
return prev.filter(r => r.uuid !== uuid)
|
||||
})
|
||||
},
|
||||
[clearAllFlagsFor],
|
||||
)
|
||||
|
||||
const cancelOrDismissDownload = useCallback((name: string) => {
|
||||
const cancelOrDismissDownload = useCallback((uuid: string) => {
|
||||
safeSetState(
|
||||
isMountedRef,
|
||||
setDownloadItems,
|
||||
)(prev => {
|
||||
const row = prev.find(r => r.name === name)
|
||||
const row = prev.find(r => r.uuid === uuid)
|
||||
|
||||
if (!row) return prev
|
||||
|
||||
if (row.status === TransferStatus.Downloading) {
|
||||
cancelledDownloadingRef.current.add(name)
|
||||
abortDownload(name)
|
||||
cancelledDownloadingRef.current.add(uuid)
|
||||
abortDownload(uuid)
|
||||
|
||||
return prev.map(r => (r.name === name ? { ...r, status: TransferStatus.Cancelled } : r))
|
||||
return prev.map(r => (r.uuid === uuid ? { ...r, status: TransferStatus.Cancelled } : r))
|
||||
}
|
||||
|
||||
cancelledDownloadingRef.current.delete(name)
|
||||
cancelledDownloadingRef.current.delete(uuid)
|
||||
|
||||
return prev.filter(r => r.name !== name)
|
||||
return prev.filter(r => r.uuid !== uuid)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const dismissAllUploads = useCallback(() => {
|
||||
setUploadItems([])
|
||||
cancelledNamesRef.current.clear()
|
||||
uploadAbortsRef.current.clear()
|
||||
queueRef.current = []
|
||||
cancelledQueuedRef.current.clear()
|
||||
cancelledUploadingRef.current.clear()
|
||||
setUploadItems([])
|
||||
}, [])
|
||||
|
||||
const dismissAllDownloads = useCallback(() => {
|
||||
@@ -684,7 +827,31 @@ export function useTransfers({ setErrorMessage }: TransferProps) {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handleFileUploaded = (e: Event) => {
|
||||
const { fileInfo } = (e as CustomEvent).detail || {}
|
||||
|
||||
if (!fileInfo) return
|
||||
|
||||
setUploadItems(prev => {
|
||||
const item = prev.find(it => it.name === fileInfo.name && it.status === TransferStatus.Uploading)
|
||||
|
||||
if (!item) return prev
|
||||
|
||||
const elapsedSec = item.startedAt ? Math.round((Date.now() - item.startedAt) / 1000) : 0
|
||||
|
||||
return updateTransferItems(prev, item.uuid, {
|
||||
percent: 100,
|
||||
status: TransferStatus.Done,
|
||||
etaSec: 0,
|
||||
elapsedSec,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener(FILE_MANAGER_EVENTS.FILE_UPLOADED, handleFileUploaded as EventListener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(FILE_MANAGER_EVENTS.FILE_UPLOADED, handleFileUploaded as EventListener)
|
||||
isMountedRef.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
Reference in New Issue
Block a user