offline_upload_queue 1.3.2 copy "offline_upload_queue: ^1.3.2" to clipboard
offline_upload_queue: ^1.3.2 copied to clipboard

Offline-first image upload queue for Flutter — persistent, retry-capable, background-aware.

1.3.2 #

Bug Fixes #

  • recoverStuckUploads retryCount sıfırlanmıyordu — Crash recovery sırasında uploading → pending geçişi doğrudan DB yazımı yapıyordu; retryCount, failureType ve errorMessage sıfırlanmıyordu. Artık markPending() metoduna delege ediliyor; tüm alanlar tutarlı biçimde sıfırlanıyor, bytesUploaded ve checksum korunuyor.
  • BackgroundTaskRunner backoff'taki görevleri saymıyorduwatchSummary dinleyicisinde yalnızca pending + uploading kontrol ediliyordu; failed (backoff'ta bekleyen) görevler atlanıyordu. Sonuç: kuyrukta yalnızca failed görev kaldığında iOS/Android zincirleme sinyali verilmiyor, görevler uygulama açılışına kadar askıya alınıyordu. Artık summary.activeCount kullanılıyor (pending + uploading + failed).
  • watchProgress phantom controller birikimi — Zaten terminal durumda olan bir görev için watchProgress() çağrıldığında StreamController oluşturulup _progressControllers haritasında kalıcı olarak birikiyordu. Artık _terminalTaskIds seti ile terminal geçişler izleniyor; terminal görevler için boş stream döndürülüyor, controller oluşturulmuyor. purge() ve dispose() çağrılarında set temizleniyor.
  • AppStateStore.dispose() sonrası anlamlı hatadispose() çağrısının ardından put/get/collection çağrıları Sembast'ın ham hatasını fırlatıyordu. Artık _isDisposed bayrağı ile StateError('AppStateStore.dispose() çağrıldıktan sonra kullanılamaz.') fırlatılıyor.

Performance #

  • getNextPending Sembast native sort — Dart tarafında tüm pending/failed kayıtlarını yükleyip sıralamak yerine Sembast'ın sortOrders kullanılıyor (priority DESC, sequenceNumber ASC). Sıralı liste üzerinde erken-çıkışlı for döngüsüyle ilk uygun kayıt bulunduğunda geri kalan değerlendirilmiyor.
  • forceUploadOnce sabit limit kaldırıldı — Sabit limit: 1000 yerine 500'lük sayfalama döngüsü kullanılıyor; 1000'den fazla pending/failed görev olan kuyruklarda tümü bypass kapsamına giriyor.

1.3.1 #

Bug Fixes (Kritik) #

  • purge() artık completed görevleri kabul ediyor — önceki sürümde SembastPersistenceRepository.purge() completed statüsü için StateError fırlatıyor, RetentionJanitor bu hatayı catch (_) {} ile yutuyordu. Sonuç: retentionPolicy tanımlanmış uygulamalarda completed görevler hiç silinmiyordu (DB sürekli şişiyor). Fix: purge() artık tüm terminal durumları kabul ediyor; completed görevlerin sandbox kopyaları upload anında zaten silindiğinden yalnızca DB kaydı kaldırılır.
  • RetentionJanitor.run() docstring güncellendi — completedAt null olan kayıtlar artık atlanmıyor, createdAt fallback kullanılıyor.
  • _purgeByStatus çift-sorgu yarış penceresi kapatıldı — dosyaları sildikten sonra aynı status filtresiyle ikinci delete çağrısı yapılıyor, arada status değişen görevler silinebilirdi. Artık snapshot key'leri korunarak Filter.inList(Field.key, keys) ile tek delete yapılıyor.
  • abortActiveUploads() backoff timer sızıntısı giderildi — abort edilen görevlerin _backoffTimers girdisi temizlenmiyor, timer ateşleniyor ve gereksiz _triggerWorker() çağrısı yapılıyordu.
  • markPending checksum silmiyordu (K-1) — pinChecksumAtEnqueue: true veya upload başlangıcında kaydedilen checksum crash recovery sonrası markPending tarafından siliniyordu. Worker dosyayı gereksiz yere tekrar hash'lemek zorunda kalıyordu; deduplicateByChecksum birlikte kullanılıyorsa dedup penceresi de kaybolabiliyordu. Artık markPending checksum'a dokunmuyor.
  • RetentionJanitor 500 görev sınırı (K-2) — run() sabit limit: 500 ile tek sayfa alıyordu; 500'den fazla süresi dolmuş görev varsa kalanlar sessizce atlanıyordu. Artık tüm eşleşen görevler sayfalanarak taranır, süresi dolmuş ID'ler toplanır, sonra purge edilir (öncelik sıralamasında öndeki taze görevler süresi dolmuşları gizlemez).
  • _handleFailure stale retryCount (K-3) — authExpired → onAuthExpired → başarısız → _handleFailure döngüsünde task.retryCount orijinal snapshot'tan geliyordu; markFailed'ın DB'de atomik artırdığı gerçek değer kullanılmıyordu. shouldPermanentlyFail ve backoff hesabı artık DB'den okunan güncel değeri kullanıyor.
  • Streaming sandbox copy disk sızıntısı (O-3) — copyToSandbox etkinken büyük dosyaların streaming kopyası başarısız olduğunda (disk dolu, permission hatası vb.) hedef dizinde yarım dosya kalıyordu. Artık hata durumunda yarım dosya temizleniyor.

Breaking (mevcut kullanıcıları etkileyebilir) #

  • BackoffStrategy.exponential artık const değil (B-11 fix): const RetryPolicy(backoff: BackoffStrategy.exponential(...)) artık derlenmez. Migration: const keyword'ünü kaldırın.

Documentation #

  • PersistenceRepository.purge(), QueueController.purge(), UploadQueue.purge(), JanitorQueueFacade.purge() docstring'leri completed'ı da kapsayacak şekilde güncellendi.
  • purgeAll docstring güçlendirildi (K-4) — aktif uploading görevlere dokunulmadığı ve tam temizlik için önce abortActiveUploads() / dispose() gerektiği açıkça belgelendi.
  • watchTasks() offset parametresi tüm facade/controller/UI imzalarında eklendi; InMemoryJanitorQueue test helper güncellendi.

1.3.0 #

Features #

  • Active dedup: with deduplicateByChecksum: true, a matching pending / uploading / failed task causes enqueue() to return that task's existing taskId (no second DB row / upload). Completed-hit behavior is unchanged (new taskId + immediate completed).
  • UploadQueue.findByChecksum public query API (optional statuses filter).
  • PersistenceRepository.findByChecksumfindCompletedByChecksum remains as a {completed} wrapper.

Breaking (custom repositories) #

  • Implement findByChecksum(checksum, {statuses}). Safe to return null if unused.

Documentation #

  • README Deduplication table (completed vs active alias); MIGRATION 1.2.x → 1.3.0.

1.2.0 #

Features #

  • Checksum bazlı dedup: UploadQueueAdvancedOptions.deduplicateByChecksum (varsayılan false) etkinken enqueue(), dosyanın checksum'ını hemen hesaplayıp aynı içeriğe sahip completed bir görev olup olmadığını kontrol eder. Eşleşme varsa döndürülen taskId hiçbir ağ isteği yapılmadan anında completed durumunda başlar — bkz. README Deduplication.
  • PersistenceRepository.findCompletedByChecksum yeni metodu (breaking for custom repository implementors — see below).
  • UploadQueueMetrics.uploadsDeduplicated — dedup edilen görev sayısı.

Bug Fixes #

  • Dedup hit artık atomik enqueue(status: completed, checksum: …) ile yazılır; önceki iki adımlı enqueue+markCompleted yolunda ara pending penceresinde worker'ın görevi alıp gerçek upload başlatma yarışı kapatıldı.
  • Dedup miss sonrası aynı dosya ikinci kez hash'lenmez — hesaplanan checksum pinlenerek upload yoluna taşınır.
  • Terminal yarışları: markCompleted / markPermanentlyFailed / updateResumableProgress artık cancelled (ve diğer terminal) durumları ezmez; chunked success yolunda _bailIfAbandoned eklendi — son chunk sırasında cancel() sonrası görevin completed olup onTaskTerminal'in iki kez ateşlenmesi engellendi.
  • pausedDueToAuth + paralel upload: auth yenileme beklerken _fillSlots yeni görev almaz.
  • abortActiveUploads: _inFlightTaskIds erken temizlenmez (dispose penceresinde yeni dequeue yok); terminal görevler pending'e diriltilmez.

Breaking (custom repositories) #

  • PersistenceRepository implementor'ları findCompletedByChecksum metodunu eklemeli. deduplicateByChecksum kullanmıyorsanız basitçe null dönebilirsiniz.
  • PersistenceRepository.enqueue optional status / checksum parametrelerini imzaya eklemeli (varsayılanlar mevcut davranışı korur).

1.1.0 #

Features #

  • Parallel uploads: UploadQueueAdvancedOptions.maxConcurrentUploads (default 1, fully backward compatible) lets the worker process several files at once. Ordering stays priority DESC, sequenceNumber ASC, but the guarantee becomes "top-N start together" instead of strict one-at-a-time completion — see README Concurrent uploads.
  • PersistenceRepository.getNextPending gains an excludeTaskIds parameter (breaking for custom repository implementors — see below) used internally to prevent the same task from being dequeued twice while maxConcurrentUploads > 1.
  • dispose() now waits for in-flight tasks that haven't yet reached the active-upload stage (e.g. still computing checksum) to settle to pending before tearing down the repository — closes a latent race where such a task could be silently stuck as uploading forever if its markPending write raced with repo shutdown.

Bug Fixes #

  • cancel() during checksum computation: the cancel token is now registered before checksum hashing starts (previously only right before the network call). Calling cancel(taskId) while a task is still hashing no longer risks the upload silently completing afterwards and overwriting the cancelled status back to completed.

Breaking (custom repositories) #

  • PersistenceRepository implementors must add the excludeTaskIds parameter to getNextPending (nullable, safe to ignore if you don't support concurrency > 1).

1.0.0 #

Bug Fixes #

  • Chunked upload loop guard: an adapter returning success with bytesAccepted: 0 (and complete: false) no longer spins forever — it is reported as a transient failure and follows the normal backoff path.
  • init() validates chunkSizeBytes / chunkThresholdBytes (≥ 1).
  • cancel() is idempotent: terminal tasks are skipped, so uploadsCancelled and onTaskTerminal fire at most once per task.
  • Resumed chunked uploads no longer double-count previously sent bytes in UploadQueueMetrics.totalBytesUploaded.
  • SembastPersistenceRepository.markCancelled clears bytesUploaded / resumableSessionId, matching the in-memory repository.

Stable #

  • API freeze for the 1.x line — public surface is the barrel export in lib/offline_upload_queue.dart.
  • Migration guide: docs/MIGRATION.md.
  • Integration test matrix: docs/integration_test_matrix.md.
  • Security README guidance: OS disk encryption / custom audited PersistenceRepository for compliance-sensitive apps.

Notes #

  • Platforms remain iOS & Android only (web/desktop out of scope).
  • Bundled Sembast encryption codec remains unaudited (unchanged warning).

0.8.0 #

Features #

  • Chunked / resumable uploads: UploadAdapter.supportsResumable + default uploadChunk(); UploadTask.bytesUploaded / resumableSessionId; PersistenceRepository.updateResumableProgress.
  • UploadQueueAdvancedOptions.chunkThresholdBytes (default 20 MiB) and chunkSizeBytes (default 8 MiB).
  • Example reference adapters: tus + S3 multipart under example/lib/adapters/.

Breaking (custom repositories) #

  • PersistenceRepository implementors must add updateResumableProgress.

Documentation #

  • README resumable section; cancel → orphan remote session caveat.

0.7.0 #

Features #

  • UploadQueueMetrics — process-lifetime counters (uploadsStarted, success/fail/cancel, retries, byte totals, running averages) via UploadQueueAdvancedOptions.onMetrics (heartbeat + terminal transitions).
  • onTaskTerminal — callback when a task reaches completed, permanentlyFailed, or cancelled. No OS notification dependency; wire your own local-notifications bridge.

Documentation #

  • README: Metrics & notifications section; optional Sentry snippet.

0.6.1 #

Documentation #

  • README Features synced with real code: Sembast persistence, priority, enqueueBatch, hardlink zero-copy sandbox, event-based lock takeover, adaptive polling.
  • Documented priority / enqueueBatch (present since earlier 0.6.x; previously under-documented).
  • Example app: first file in a multi-select batch gets priority: 1 so priority ordering is visible in the demo.

Notes #

  • CHANGELOG 0.3.0 “SQLite tableUpdates” describes the original Drift-era lock signal. After 0.5.0 the equivalent is Sembast-backed event-based lock takeover (same UX: resume when another isolate releases the lock).

0.6.0 #

Breaking Changes #

  • API Değişikliği: PersistenceRepository.enqueue imzasından sequenceNumber parametresi kaldırıldı. Sequence numarası artık repository tarafından (transaction içerisinde güvenli şekilde) otomatik üretiliyor. Özel PersistenceRepository implementasyonlarının enqueue imzalarını güncellemeleri gerekir.
  • PersistenceRepository.init() artık crash recovery yapmaz; recoverStuckUploads() worker kilidi alındıktan sonra çağrılmalıdır (çift yükleme yarışını önlemek için). QueueController bunu otomatik yönetir.
  • retry() / purge() yalnızca permanentlyFailed / cancelled görevleri kabul eder; aksi halde StateError.
  • Özel PersistenceRepository implementasyonları için yeni üyeler: getTask, hasProgressListener, getNextPending(..., onlyTaskIds:).

Bug Fixes #

  • dispose() sonrası init() artık çalışır (late final kaldırıldı).
  • BackgroundTaskRunner paylaşılan (iOS) kuyruğu dispose etmez; abortActiveUploads() eklendi.
  • watchProgress dinleyici varken gerçekten onProgress bağlanır.
  • cancel() / dispose() sonrası adapter failure dönüşü görevi failed ile ezmez.
  • updateHeartbeat yalnızca kilit sahibi için yazar.
  • forceUploadOnce öncelik açlığı giderildi; pause()/resume() watchSummary abonelerini günceller.
  • Sequence üretimi O(1) meta sayaç kullanır; yayın arşivinden build/ ve geçici kök dosyalar çıkarıldı.

0.5.1 #

Fixes & Improvements #

  • Performans: Büyük dosyalarda (örn. 50MB+) enqueue işlemi sırasında oluşan RAM spike'ı önlemek için checksum hesaplaması streaming (chunked SHA-256) kullanacak şekilde güncellendi.
  • Hafıza: Tamamlanan (completed, cancelled, permanentlyFailed) görevlerde progress stream controller'larının map'te birikmesine neden olan hafıza sızıntısı giderildi.
  • Güvenilirlik: Dosyaların sandbox dizinine kopyalanması sırasında dosya uzantısının hesaplanmasındaki bir kırılganlık (path paketi kullanılarak) giderildi.

0.5.0 #

Breaking Changes #

  • Persistence backend değişti: SQLite/Drift'ten sembast'a geçildi.
    • Native binary bağımlılığı (sqlite3_flutter_libs) kaldırıldı — pure-Dart backend.
    • drift ve sqlite3_flutter_libs bağımlılıkları kaldırıldı.
    • build_runner / drift_dev artık gerekmiyor — codegen adımı yok.
    • database.dart ve tables.dart public export'tan kaldırıldı; SembastPersistenceRepository export edildi (ileri düzey kullanım için).
  • PersistenceRepository interface'i değişmedi — özel implementasyonlar etkilenmez.

Encryption (Uyarı ile) #

  • encryptionKey parametresi artık sembast'ın codec mekanizmasına bağlı. Önemli: Kullanılan codec (Salsa20+SHA256), sembast kaynak deposundaki örnek bir implementasyondur ve bağımsız güvenlik denetiminden geçmemiştir. Compliance gerektiren kullanım senaryoları için bağımsız denetlenmiş bir şifreleme çözümü tercih edin.

0.4.0 #

Security #

  • Encryption Support: Added encryptionKey option to UploadQueue allowing the database to be fully encrypted at rest (typically requires a federated SQLCipher package like sqlcipher_flutter_libs).
  • Metadata Encryption: Added MetadataCodec interface to UploadQueue for encrypting only PII data inside metadata fields without encrypting the entire database.

0.3.0 #

Performance #

  • Event-Based Lock Takeover: QueueController now listens to SQLite lock table updates (tableUpdates) to immediately resume uploads when a worker releases a lock, eliminating the default 30s polling delay in multi-isolate setups. (Historical — Drift/SQLite era. From 0.5.0 the persistence backend is Sembast; lock takeover remains event-based with the same intent.)
  • Adaptive Polling: In background execution contexts with short deadlines (e.g. iOS BGTaskScheduler), the polling interval is adaptively reduced to prevent missing the execution window.

0.2.0 #

Performance #

  • Zero-Copy Sandbox: copyToSandbox now attempts to use hardlinks (ln) first on compatible filesystems to eliminate disk I/O and duplication overhead.
  • Streaming Copy: Introduced sandboxCopyThresholdBytes in UploadQueueAdvancedOptions. Files larger than this threshold fallback to an asynchronous chunked streaming copy instead of blocking File.copy(), saving memory on large files.

0.1.0 #

Initial public release.

Features #

  • Offline-first persistent upload queue backed by SQLite (via Drift).
  • Sequential processing with configurable maxAttempts and exponential backoff retry (BackoffStrategy.exponential / BackoffStrategy.fixed).
  • Wi-Fi only mode (wifiOnly: true) with cellular override via forceUploadOnce().
  • Reactive streams: watchSummary(), watchTasks(), watchProgress().
  • Disk usage tracking: estimatedDiskUsageBytes and configurable onDiskUsageWarning callback.
  • SHA-256 checksum verification against optional server-side checksum.
  • copyToSandbox: true (default) — files are copied to a package-managed sandbox directory on enqueue so originals can be deleted safely.
  • Stale-lock recovery: uploading → pending on restart after crash.
  • Worker heartbeat and atomic lock acquisition (SQLite single-writer guarantee).
  • iOS background sync via BGTaskScheduler (IosBackgroundChannel).
  • Android background sync via Workmanager (AndroidBackgroundRunner).
  • Pluggable UploadAdapter interface (default: RestUploadAdapter with Dio).
  • Pluggable ConnectivityMonitor interface (default: DefaultConnectivityMonitor with reachability test).
  • Pluggable PersistenceRepository interface for custom storage backends.
  • onAuthExpired callback for token-refresh integration.
  • onLog hook for routing internal events to Sentry / Crashlytics.
  • Multiple independent queues via boxName parameter.
3
likes
160
points
173
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Offline-first image upload queue for Flutter — persistent, retry-capable, background-aware.

Repository (GitHub)
View/report issues
Contributing

Topics

#flutter #upload #offline-first #queue #sembast

License

MIT (license)

Dependencies

connectivity_plus, crypto, dio, encrypt, flutter, path, path_provider, sembast, uuid, workmanager

More

Packages that depend on offline_upload_queue