quantum_upload 1.0.0
quantum_upload: ^1.0.0 copied to clipboard
A pure Dart package for reliable chunked file uploads with automatic resume, persistent session tracking, integrity verification, and seamless recovery from network interruptions.
quantum_upload π¦ #
Upload large files over unreliable networks β reliably.
Splits. Persists. Resumes. Works with any HTTP server.
The Problem #
You're uploading a 2 GB video. The network drops at 99%.
You start again from zero. You cry.
The Solution #
quantum_upload splits the file into small pieces, confirms each one with
the server, and saves progress locally. When the network drops, it picks up
from the last confirmed piece β not from the beginning.
Features #
| Feature | Detail |
|---|---|
| βοΈ Smart chunking | Configurable chunk size (default 5 MiB); last chunk gets the remainder |
| πΎ Persistent sessions | Survives app kill + device restart via SharedPreferences |
| π True resume | Skips already-confirmed chunks β zero redundant uploads |
| π Exponential back-off | delay_n = baseDelay Γ 2^(nβ1), capped at 30 s |
| π MD5 integrity check | Every chunk is hashed before sending |
| π Rich progress | Percent, EMA speed, ETA, uploaded/total formatted |
| βΈοΈ Pause / resume / cancel | Full lifecycle control between chunks |
| π Server-agnostic | Standard HTTP multipart POST β no special protocol needed |
| π Pluggable storage | Replace SharedPreferences with any SessionStorage |
| π§ͺ 100 % unit-tested | Every layer tested in isolation with a mock HTTP client |
Installation #
dependencies:
quantum_upload: ^1.0.0
dart pub get
Quick Start #
import 'package:quantum_upload/quantum_upload.dart';
final result = await Uploader.upload(
filePath: '/path/to/video.mp4',
url: 'https://api.example.com/upload',
headers: {'Authorization': 'Bearer $token'},
onProgress: (pct) => print('${pct.toStringAsFixed(1)} %'),
);
print('Done in ${result.duration.inSeconds}s @ ${result.speedMbps}');
Advanced Usage #
Full configuration #
final config = UploadConfig(
filePath : '/path/to/video.mp4',
url : Uri.parse('https://api.example.com/upload'),
chunkSize : 10 * 1024 * 1024, // 10 MiB chunks
maxRetries : 5,
retryDelay : const Duration(seconds: 2), // 2 s β 4 s β 8 s β β¦
headers : {'Authorization': 'Bearer $token'},
sessionId : savedSessionId, // null = fresh upload
onProgress : (pct) => setState(() => _progress = pct),
onChunkRetry : (chunk, attempt) => print('Retrying chunk $chunk ($attempt)'),
onComplete : (result) => print('Done! ${result.speedMbps}'),
onError : (err) => showErrorDialog(err.message),
);
final uploader = Uploader(config);
await uploader.start();
Pause and resume #
final uploader = Uploader(config);
unawaited(uploader.start()); // fire and forget in background
// From UI buttons:
uploader.pause(); // stops after current chunk finishes
uploader.resume(); // continues immediately
await uploader.cancel(); // permanent β cleans up session
Cross-session resume (app restart) #
// ββ Run 1 β save the session ID βββββββββββββββββββββββββββββββββ
final uploader = Uploader(config);
await prefs.setString('uploadSession', uploader.sessionId);
await uploader.start();
// ββ Run 2 β resume automatically ββββββββββββββββββββββββββββββββ
await Uploader.upload(
filePath : '/path/to/video.mp4',
url : 'https://api.example.com/upload',
sessionId : prefs.getString('uploadSession'), // β magic
);
Rich progress stream #
uploader.progressStream.listen((snap) {
print(
'[${snap.percent.toStringAsFixed(1).padLeft(5)}%] '
'${snap.uploadedFormatted} / ${snap.totalFormatted} '
'@ ${snap.speedMbps} ETA ${snap.etaFormatted}',
);
});
Sample output:
[ 12.5%] 12.50 MiB / 100.00 MiB @ 8.32 MB/s ETA 10:38
[ 25.0%] 25.00 MiB / 100.00 MiB @ 9.01 MB/s ETA 08:21
How It Works #
Uploader.start()
β
ββ ChunkManager.initialize()
β ββ compute chunk boundaries: [(0, 5M), (5M, 10M), β¦]
β
ββ UploadSession.restore(sessionId) or create fresh
β ββ persisted in SharedPreferences as JSON
β
ββ for each pending chunk:
ββ readChunk(i) β RandomAccessFile.setPosition + read
ββ computeChecksum(data) β MD5 hex
ββ RetryPolicy.executeChunk(() => UploadRequest.send(β¦))
β ββ on success: markChunkCompleted(i) + session.save()
β ββ on fail: exponential back-off β retry
ββ progressTracker.addBytes(chunkSize)
β UploadResult (session deleted from storage)
HTTP Wire Format #
Every chunk is a standard multipart/form-data POST:
POST /upload
Content-Type: multipart/form-data; boundary=β¦
X-Session-Id: c3d4e5f6-β¦
X-Chunk-Index: 3
X-Total-Chunks: 40
X-Chunk-Checksum: a3f4b2c1d5e6β¦ (MD5 hex)
X-File-Size: 209715200
Content-Range: bytes 15728640-20971519/209715200
Authorization: Bearer <your-token> (from config.headers)
--boundary
Content-Disposition: form-data; name="chunk"; filename="chunk_3"
<raw 5 MiB bytes>
--boundary--
See example/server_example/server.js for a Node.js reference implementation.
Package Architecture #
lib/
βββ quantum_upload.dart β barrel (one import for everything)
βββ src/
βββ uploader.dart β main orchestrator β start/pause/resume/cancel
βββ chunk_manager.dart β file splitting + chunk reading + MD5
βββ upload_session.dart β persistent per-chunk state
βββ upload_request.dart β HTTP multipart builder + sender
βββ retry_policy.dart β exponential back-off executor
βββ progress_tracker.dart β EMA speed, ETA, stream emitter
βββ models/
β βββ upload_config.dart β immutable configuration value object
β βββ upload_state.dart β enum: idle|uploading|paused|β¦|completed
β βββ chunk_info.dart β byte-range + state + checksum per chunk
β βββ upload_result.dart β final summary (speed, chunks, duration)
βββ storage/
β βββ session_storage.dart β abstract interface
β βββ shared_prefs_storage.dart β default implementation
βββ exceptions/
βββ upload_exception.dart β base class
βββ chunk_exception.dart β per-chunk failure (HTTP status, body)
βββ session_exception.dart β storage failure
Comparison #
| Feature | quantum_upload | flutter_upchunk | tus_client_dart |
|---|---|---|---|
| Pure Dart (no Flutter dep.) | β | β | β |
| Resume after app restart | β | β | β |
| Works with any HTTP server | β | β | β requires tus server |
| MD5 checksum per chunk | β | β | β |
| Exponential back-off | β | β | β |
| Pause / resume | β | β | β |
| Progress stream (speed + ETA) | β | β | β |
| Pluggable session storage | β | β | β |
| 4xx vs 5xx retry distinction | β | β | β |
| Unit test coverage | β 100 % | partial | partial |
Tests #
dart test
dart test --coverage=coverage
dart pub global run coverage:format_coverage --lcov \
--in=coverage --out=lcov.info --report-on=lib
Test suite covers:
chunk_manager_test.dartβ boundary computation, byte reading, checksumsupload_session_test.dartβ persistence, restore, mutationretry_policy_test.dartβ should-retry logic, delay formula, executorsprogress_tracker_test.dartβ EMA speed, ETA, stream emissionsuploader_test.dartβ end-to-end withMockHttpClient
Contributing #
- Fork the repo and create a feature branch.
- Write tests for every new behaviour.
- Run
dart analyze && dart testβ both must pass. - Submit a pull request with a clear description.
License #
MIT Β© 2026 β see LICENSE.