Scale chunk size by total transfer size

codex/fix-file-upload-progress-bar-behavior
Alex Kern 6 months ago
parent ffd64d8d99
commit a527114386

@ -70,7 +70,8 @@
"lint-staged": "^15.0.0", "lint-staged": "^15.0.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"typescript-eslint": "^8.18.2" "typescript-eslint": "^8.18.2",
"@eslint/js": "^9.30.0"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

@ -75,6 +75,9 @@ importers:
specifier: ^3.23.8 specifier: ^3.23.8
version: 3.25.67 version: 3.25.67
devDependencies: devDependencies:
'@eslint/js':
specifier: ^9.30.0
version: 9.30.0
'@types/debug': '@types/debug':
specifier: ^4.1.12 specifier: ^4.1.12
version: 4.1.12 version: 4.1.12

@ -207,8 +207,14 @@ export function useDownloader(uploaderPeerID: string): {
console.error('[Downloader] no stream found for', message.fileName) console.error('[Downloader] no stream found for', message.fileName)
return return
} }
setBytesDownloaded((bd) => bd + (message.bytes as ArrayBuffer).byteLength) const chunkSize = (message.bytes as ArrayBuffer).byteLength
setBytesDownloaded((bd) => bd + chunkSize)
fileStream.enqueue(new Uint8Array(message.bytes as ArrayBuffer)) fileStream.enqueue(new Uint8Array(message.bytes as ArrayBuffer))
dataConnection.send({
type: MessageType.Ack,
fileName: message.fileName,
offset: message.offset + chunkSize,
} as z.infer<typeof Message>)
if (message.final) { if (message.final) {
console.log('[Downloader] finished receiving', message.fileName) console.log('[Downloader] finished receiving', message.fileName)
fileStream.close() fileStream.close()

@ -9,8 +9,28 @@ import { decodeMessage, Message, MessageType } from '../messages'
import { getFileName } from '../fs' import { getFileName } from '../fs'
import { setRotating } from './useRotatingSpinner' import { setRotating } from './useRotatingSpinner'
// TODO(@kern): Test for better values const MIN_CHUNK_SIZE = 128 * 1024 // 128 KB
const MAX_CHUNK_SIZE = 256 * 1024 // 256 KB const MAX_CHUNK_SIZE_CAP = 1024 * 1024 // 1 MB
function calculateChunkSize(totalTransferSize: number): number {
const MIN_TRANSFER = 1 * 1024 * 1024 // 1 MB
const MAX_TRANSFER = 1 * 1024 * 1024 * 1024 // 1 GB
if (totalTransferSize <= MIN_TRANSFER) {
return MIN_CHUNK_SIZE
}
if (totalTransferSize >= MAX_TRANSFER) {
return MAX_CHUNK_SIZE_CAP
}
const ratio =
(totalTransferSize - MIN_TRANSFER) / (MAX_TRANSFER - MIN_TRANSFER)
return (
MIN_CHUNK_SIZE + Math.round(ratio * (MAX_CHUNK_SIZE_CAP - MIN_CHUNK_SIZE))
)
}
function validateOffset( function validateOffset(
files: UploadedFile[], files: UploadedFile[],
@ -31,6 +51,8 @@ export function useUploaderConnections(
files: UploadedFile[], files: UploadedFile[],
password: string, password: string,
): Array<UploaderConnection> { ): Array<UploaderConnection> {
const totalTransferSize = files.reduce((sum, f) => sum + f.size, 0)
const MAX_CHUNK_SIZE = calculateChunkSize(totalTransferSize)
const [connections, setConnections] = useState<Array<UploaderConnection>>([]) const [connections, setConnections] = useState<Array<UploaderConnection>>([])
useEffect(() => { useEffect(() => {
@ -237,33 +259,10 @@ export function useUploaderConnections(
final, final,
} }
conn.send(request) conn.send(request)
offset = end
updateConnection((draft) => { if (!final) {
offset = end sendNextChunkAsync()
if (final) { }
console.log(
'[UploaderConnections] completed file',
fileName,
'- file',
draft.completedFiles + 1,
'of',
draft.totalFiles,
)
return {
...draft,
status: UploaderConnectionStatus.Ready,
completedFiles: draft.completedFiles + 1,
currentFileProgress: 0,
}
} else {
sendNextChunkAsync()
return {
...draft,
uploadingOffset: end,
currentFileProgress: end / file.size,
}
}
})
}, 0) }, 0)
} }
@ -309,6 +308,36 @@ export function useUploaderConnections(
break break
} }
case MessageType.Ack: {
const { fileName: ackFileName, offset: ackOffset } = message
try {
const ackFile = validateOffset(files, ackFileName, ackOffset)
updateConnection((draft) => {
if (draft.uploadingFileName !== ackFileName) {
return draft
}
const completed = ackOffset >= ackFile.size
return {
...draft,
uploadingOffset: ackOffset,
currentFileProgress: Math.min(ackOffset / ackFile.size, 1),
...(completed
? {
status: UploaderConnectionStatus.Ready,
completedFiles: draft.completedFiles + 1,
uploadingFileName: undefined,
}
: {}),
}
})
} catch (err) {
console.error('[UploaderConnections] error handling ack:', err)
}
break
}
case MessageType.Done: { case MessageType.Done: {
console.log( console.log(
'[UploaderConnections] transfer completed successfully', '[UploaderConnections] transfer completed successfully',

@ -6,6 +6,7 @@ export enum MessageType {
Start = 'Start', Start = 'Start',
Chunk = 'Chunk', Chunk = 'Chunk',
Pause = 'Pause', Pause = 'Pause',
Ack = 'Ack',
Done = 'Done', Done = 'Done',
Error = 'Error', Error = 'Error',
PasswordRequired = 'PasswordRequired', PasswordRequired = 'PasswordRequired',
@ -48,6 +49,12 @@ export const ChunkMessage = z.object({
final: z.boolean(), final: z.boolean(),
}) })
export const AckMessage = z.object({
type: z.literal(MessageType.Ack),
fileName: z.string(),
offset: z.number(),
})
export const DoneMessage = z.object({ export const DoneMessage = z.object({
type: z.literal(MessageType.Done), type: z.literal(MessageType.Done),
}) })
@ -80,6 +87,7 @@ export const Message = z.discriminatedUnion('type', [
InfoMessage, InfoMessage,
StartMessage, StartMessage,
ChunkMessage, ChunkMessage,
AckMessage,
DoneMessage, DoneMessage,
ErrorMessage, ErrorMessage,
PasswordRequiredMessage, PasswordRequiredMessage,

Loading…
Cancel
Save