feat: support parallel multi-file uploads
The browser upload flow was built around one selected file and one global upload state. That made the existing chunk pool useful for a single file, but users could not start several selected files at the same time. Refactor the browser state into per-file upload items. Each selected file now has its own upload record, completed-chunk set, abort controller, retry state, progress row, and saved IndexedDB resume record. The picker accepts multiple files, `Start all` and `Resume all` use a bounded file-level pool, and each file keeps the existing bounded chunk pool. This keeps parallel uploads useful without letting one large selection create unbounded request fan-out. Keep the server API unchanged. Each file still receives a separate server upload id, and server-side progress remains authoritative before any missing chunks are scheduled. Terminal conflicts still stop the affected file without overwriting completed data. Update the user-facing markup, styles, project docs, and test checklist for the multi-file scheduler. Add a server regression test that interleaves two uploads and verifies the completed files contain exactly their own bytes. Test Plan: - just check - git diff --check
This commit is contained in:
@@ -26,9 +26,9 @@ The program should stay simple:
|
||||
|
||||
The browser owns file selection and chunk scheduling.
|
||||
|
||||
- Let the user pick one file.
|
||||
- Let the user pick one or more files.
|
||||
- Slice it into fixed-size chunks with `Blob.slice()`.
|
||||
- Upload a few chunks concurrently.
|
||||
- Upload a few files concurrently, with a separate chunk pool per file.
|
||||
- Retry failed chunks with exponential backoff.
|
||||
- Persist pending upload state in IndexedDB.
|
||||
- Use the File System Access API when available so the same local file can be
|
||||
@@ -194,11 +194,12 @@ The server should not delete staging data until assembly succeeds.
|
||||
|
||||
### First Upload
|
||||
|
||||
1. User selects a file.
|
||||
2. Browser calls `POST /api/uploads`.
|
||||
3. Browser stores the returned `upload_id` and file handle in IndexedDB.
|
||||
4. Browser uploads missing chunks with a small concurrency pool.
|
||||
5. Browser calls `/complete` when all chunks are uploaded.
|
||||
1. User selects one or more files.
|
||||
2. Browser creates one selected upload row per file.
|
||||
3. Browser calls `POST /api/uploads` once for each file being started.
|
||||
4. Browser stores each returned `upload_id` and file handle in IndexedDB.
|
||||
5. Browser uploads missing chunks with bounded file and chunk concurrency pools.
|
||||
6. Browser calls `/complete` for each file when all of its chunks are uploaded.
|
||||
|
||||
### After Interruption
|
||||
|
||||
@@ -239,7 +240,8 @@ Start with these defaults:
|
||||
|
||||
```text
|
||||
chunk size: 16 MiB
|
||||
concurrency: 3
|
||||
file concurrency: 3
|
||||
chunk concurrency per file: 3
|
||||
max retries per chunk: 5
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user