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",
"prettier": "^3.0.0",
"typescript": "^5.0.0",
"typescript-eslint": "^8.18.2"
"typescript-eslint": "^8.18.2",
"@eslint/js": "^9.30.0"
},
"husky": {
"hooks": {

@ -75,6 +75,9 @@ importers:
specifier: ^3.23.8
version: 3.25.67
devDependencies:
'@eslint/js':
specifier: ^9.30.0
version: 9.30.0
'@types/debug':
specifier: ^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)
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))
dataConnection.send({
type: MessageType.Ack,
fileName: message.fileName,
offset: message.offset + chunkSize,
} as z.infer<typeof Message>)
if (message.final) {
console.log('[Downloader] finished receiving', message.fileName)
fileStream.close()

@ -9,8 +9,28 @@ import { decodeMessage, Message, MessageType } from '../messages'
import { getFileName } from '../fs'
import { setRotating } from './useRotatingSpinner'
// TODO(@kern): Test for better values
const MAX_CHUNK_SIZE = 256 * 1024 // 256 KB
const MIN_CHUNK_SIZE = 128 * 1024 // 128 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(
files: UploadedFile[],
@ -31,6 +51,8 @@ export function useUploaderConnections(
files: UploadedFile[],
password: string,
): Array<UploaderConnection> {
const totalTransferSize = files.reduce((sum, f) => sum + f.size, 0)
const MAX_CHUNK_SIZE = calculateChunkSize(totalTransferSize)
const [connections, setConnections] = useState<Array<UploaderConnection>>([])
useEffect(() => {
@ -237,33 +259,10 @@ export function useUploaderConnections(
final,
}
conn.send(request)
updateConnection((draft) => {
offset = end
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,
}
}
})
offset = end
if (!final) {
sendNextChunkAsync()
}
}, 0)
}
@ -309,6 +308,36 @@ export function useUploaderConnections(
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: {
console.log(
'[UploaderConnections] transfer completed successfully',

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

Loading…
Cancel
Save