offline_upload_queue 1.3.3
offline_upload_queue: ^1.3.3 copied to clipboard
Offline-first image upload queue for Flutter — persistent, retry-capable, background-aware.
1.3.3 #
Bug Fixes #
- pub.dev screenshots fixed:
.jpegve.jpguzantılı pub.dev galeri görselleri desteklenen.pngformatına dönüştürüldü.
1.3.2 #
Bug Fixes #
recoverStuckUploadsretryCount sıfırlanmıyordu — Crash recovery sırasındauploading → pendinggeçişi doğrudan DB yazımı yapıyordu;retryCount,failureTypeveerrorMessagesıfırlanmıyordu. ArtıkmarkPending()metoduna delege ediliyor; tüm alanlar tutarlı biçimde sıfırlanıyor,bytesUploadedvechecksumkorunuyor.BackgroundTaskRunnerbackoff'taki görevleri saymıyordu —watchSummarydinleyicisinde yalnızcapending + uploadingkontrol ediliyordu;failed(backoff'ta bekleyen) görevler atlanıyordu. Sonuç: kuyrukta yalnızcafailedgörev kaldığında iOS/Android zincirleme sinyali verilmiyor, görevler uygulama açılışına kadar askıya alınıyordu. Artıksummary.activeCountkullanılıyor (pending + uploading + failed).watchProgressphantom controller birikimi — Zaten terminal durumda olan bir görev içinwatchProgress()çağrıldığındaStreamControlleroluşturulup_progressControllersharitasında kalıcı olarak birikiyordu. Artık_terminalTaskIdsseti ile terminal geçişler izleniyor; terminal görevler için boş stream döndürülüyor, controller oluşturulmuyor.purge()vedispose()çağrılarında set temizleniyor.AppStateStore.dispose()sonrası anlamlı hata —dispose()çağrısının ardındanput/get/collectionçağrıları Sembast'ın ham hatasını fırlatıyordu. Artık_isDisposedbayrağı ileStateError('AppStateStore.dispose() çağrıldıktan sonra kullanılamaz.')fırlatılıyor.
Performance #
getNextPendingSembast native sort — Dart tarafında tüm pending/failed kayıtlarını yükleyip sıralamak yerine Sembast'ınsortOrderskullanılıyor (priority DESC, sequenceNumber ASC). Sıralı liste üzerinde erken-çıkışlıfordöngüsüyle ilk uygun kayıt bulunduğunda geri kalan değerlendirilmiyor.forceUploadOncesabit limit kaldırıldı — Sabitlimit: 1000yerine 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ıkcompletedgörevleri kabul ediyor — önceki sürümdeSembastPersistenceRepository.purge()completedstatüsü içinStateErrorfırlatıyor,RetentionJanitorbu hatayıcatch (_) {}ile yutuyordu. Sonuç:retentionPolicytanımlanmış uygulamalardacompletedgörevler hiç silinmiyordu (DB sürekli şişiyor). Fix:purge()artık tüm terminal durumları kabul ediyor;completedgörevlerin sandbox kopyaları upload anında zaten silindiğinden yalnızca DB kaydı kaldırılır.RetentionJanitor.run()docstring güncellendi —completedAtnull olan kayıtlar artık atlanmıyor,createdAtfallback kullanılıyor._purgeByStatusçift-sorgu yarış penceresi kapatıldı — dosyaları sildikten sonra aynı status filtresiyle ikincideleteçağrısı yapılıyor, arada status değişen görevler silinebilirdi. Artık snapshot key'leri korunarakFilter.inList(Field.key, keys)ile tek delete yapılıyor.abortActiveUploads()backoff timer sızıntısı giderildi — abort edilen görevlerin_backoffTimersgirdisi temizlenmiyor, timer ateşleniyor ve gereksiz_triggerWorker()çağrısı yapılıyordu.markPendingchecksum silmiyordu (K-1) —pinChecksumAtEnqueue: trueveya upload başlangıcında kaydedilen checksum crash recovery sonrasımarkPendingtarafından siliniyordu. Worker dosyayı gereksiz yere tekrar hash'lemek zorunda kalıyordu;deduplicateByChecksumbirlikte kullanılıyorsa dedup penceresi de kaybolabiliyordu. ArtıkmarkPendingchecksum'a dokunmuyor.RetentionJanitor500 görev sınırı (K-2) —run()sabitlimit: 500ile 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)._handleFailurestale retryCount (K-3) —authExpired → onAuthExpired → başarısız → _handleFailuredöngüsündetask.retryCountorijinal snapshot'tan geliyordu;markFailed'ın DB'de atomik artırdığı gerçek değer kullanılmıyordu.shouldPermanentlyFailve backoff hesabı artık DB'den okunan güncel değeri kullanıyor.- Streaming sandbox copy disk sızıntısı (O-3) —
copyToSandboxetkinken 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.exponentialartıkconstdeğil (B-11 fix):const RetryPolicy(backoff: BackoffStrategy.exponential(...))artık derlenmez. Migration:constkeyword'ünü kaldırın.
Documentation #
PersistenceRepository.purge(),QueueController.purge(),UploadQueue.purge(),JanitorQueueFacade.purge()docstring'lericompleted'ı da kapsayacak şekilde güncellendi.purgeAlldocstring güçlendirildi (K-4) — aktifuploadinggörevlere dokunulmadığı ve tam temizlik için önceabortActiveUploads()/dispose()gerektiği açıkça belgelendi.watchTasks()offsetparametresi tüm facade/controller/UI imzalarında eklendi;InMemoryJanitorQueuetest helper güncellendi.
1.3.0 #
Features #
- Active dedup: with
deduplicateByChecksum: true, a matchingpending/uploading/failedtask causesenqueue()to return that task's existingtaskId(no second DB row / upload). Completed-hit behavior is unchanged (new taskId + immediatecompleted). UploadQueue.findByChecksumpublic query API (optionalstatusesfilter).PersistenceRepository.findByChecksum—findCompletedByChecksumremains as a{completed}wrapper.
Breaking (custom repositories) #
- Implement
findByChecksum(checksum, {statuses}). Safe to returnnullif 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ılanfalse) etkinkenenqueue(), dosyanın checksum'ını hemen hesaplayıp aynı içeriğe sahipcompletedbir görev olup olmadığını kontrol eder. Eşleşme varsa döndürülen taskId hiçbir ağ isteği yapılmadan anındacompleteddurumunda başlar — bkz. README Deduplication. PersistenceRepository.findCompletedByChecksumyeni 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+markCompletedyolunda arapendingpenceresinde 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/updateResumableProgressartıkcancelled(ve diğer terminal) durumları ezmez; chunked success yolunda_bailIfAbandonedeklendi — son chunk sırasındacancel()sonrası görevincompletedoluponTaskTerminal'in iki kez ateşlenmesi engellendi. pausedDueToAuth+ paralel upload: auth yenileme beklerken_fillSlotsyeni görev almaz.abortActiveUploads:_inFlightTaskIdserken temizlenmez (dispose penceresinde yeni dequeue yok); terminal görevlerpending'e diriltilmez.
Breaking (custom repositories) #
PersistenceRepositoryimplementor'larıfindCompletedByChecksummetodunu eklemeli.deduplicateByChecksumkullanmıyorsanız basitçenulldönebilirsiniz.PersistenceRepository.enqueueoptionalstatus/checksumparametrelerini imzaya eklemeli (varsayılanlar mevcut davranışı korur).
1.1.0 #
Features #
- Parallel uploads:
UploadQueueAdvancedOptions.maxConcurrentUploads(default1, fully backward compatible) lets the worker process several files at once. Ordering stayspriority DESC, sequenceNumber ASC, but the guarantee becomes "top-N start together" instead of strict one-at-a-time completion — see README Concurrent uploads. PersistenceRepository.getNextPendinggains anexcludeTaskIdsparameter (breaking for custom repository implementors — see below) used internally to prevent the same task from being dequeued twice whilemaxConcurrentUploads > 1.dispose()now waits for in-flight tasks that haven't yet reached the active-upload stage (e.g. still computing checksum) to settle topendingbefore tearing down the repository — closes a latent race where such a task could be silently stuck asuploadingforever if itsmarkPendingwrite 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). Callingcancel(taskId)while a task is still hashing no longer risks the upload silently completing afterwards and overwriting thecancelledstatus back tocompleted.
Breaking (custom repositories) #
PersistenceRepositoryimplementors must add theexcludeTaskIdsparameter togetNextPending(nullable, safe to ignore if you don't support concurrency > 1).
1.0.0 #
Bug Fixes #
- Chunked upload loop guard: an adapter returning
successwithbytesAccepted: 0(andcomplete: false) no longer spins forever — it is reported as a transient failure and follows the normal backoff path. init()validateschunkSizeBytes/chunkThresholdBytes(≥ 1).cancel()is idempotent: terminal tasks are skipped, souploadsCancelledandonTaskTerminalfire at most once per task.- Resumed chunked uploads no longer double-count previously sent bytes in
UploadQueueMetrics.totalBytesUploaded. SembastPersistenceRepository.markCancelledclearsbytesUploaded/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
PersistenceRepositoryfor 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+ defaultuploadChunk();UploadTask.bytesUploaded/resumableSessionId;PersistenceRepository.updateResumableProgress. UploadQueueAdvancedOptions.chunkThresholdBytes(default 20 MiB) andchunkSizeBytes(default 8 MiB).- Example reference adapters: tus + S3 multipart under
example/lib/adapters/.
Breaking (custom repositories) #
PersistenceRepositoryimplementors must addupdateResumableProgress.
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) viaUploadQueueAdvancedOptions.onMetrics(heartbeat + terminal transitions).onTaskTerminal— callback when a task reachescompleted,permanentlyFailed, orcancelled. 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: 1so 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.enqueueimzasındansequenceNumberparametresi kaldırıldı. Sequence numarası artık repository tarafından (transaction içerisinde güvenli şekilde) otomatik üretiliyor. ÖzelPersistenceRepositoryimplementasyonlarınınenqueueimzaları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).QueueControllerbunu otomatik yönetir.retry()/purge()yalnızcapermanentlyFailed/cancelledgörevleri kabul eder; aksi haldeStateError.- Özel
PersistenceRepositoryimplementasyonları için yeni üyeler:getTask,hasProgressListener,getNextPending(..., onlyTaskIds:).
Bug Fixes #
dispose()sonrasıinit()artık çalışır (late finalkaldırıldı).BackgroundTaskRunnerpaylaşılan (iOS) kuyruğu dispose etmez;abortActiveUploads()eklendi.watchProgressdinleyici varken gerçektenonProgressbağlanır.cancel()/dispose()sonrası adapterfailuredönüşü görevifailedile ezmez.updateHeartbeatyalnızca kilit sahibi için yazar.forceUploadOnceöncelik açlığı giderildi;pause()/resume()watchSummaryabonelerini 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 (
pathpaketi 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. driftvesqlite3_flutter_libsbağımlılıkları kaldırıldı.build_runner/drift_devartık gerekmiyor — codegen adımı yok.database.dartvetables.dartpublic export'tan kaldırıldı;SembastPersistenceRepositoryexport edildi (ileri düzey kullanım için).
- Native binary bağımlılığı (
PersistenceRepositoryinterface'i değişmedi — özel implementasyonlar etkilenmez.
Encryption (Uyarı ile) #
encryptionKeyparametresi 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
encryptionKeyoption toUploadQueueallowing the database to be fully encrypted at rest (typically requires a federated SQLCipher package likesqlcipher_flutter_libs). - Metadata Encryption: Added
MetadataCodecinterface toUploadQueuefor encrypting only PII data insidemetadatafields without encrypting the entire database.
0.3.0 #
Performance #
- Event-Based Lock Takeover:
QueueControllernow 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:
copyToSandboxnow attempts to use hardlinks (ln) first on compatible filesystems to eliminate disk I/O and duplication overhead. - Streaming Copy: Introduced
sandboxCopyThresholdBytesinUploadQueueAdvancedOptions. Files larger than this threshold fallback to an asynchronous chunked streaming copy instead of blockingFile.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
maxAttemptsand exponential backoff retry (BackoffStrategy.exponential/BackoffStrategy.fixed). - Wi-Fi only mode (
wifiOnly: true) with cellular override viaforceUploadOnce(). - Reactive streams:
watchSummary(),watchTasks(),watchProgress(). - Disk usage tracking:
estimatedDiskUsageBytesand configurableonDiskUsageWarningcallback. - 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 → pendingon 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
UploadAdapterinterface (default:RestUploadAdapterwith Dio). - Pluggable
ConnectivityMonitorinterface (default:DefaultConnectivityMonitorwith reachability test). - Pluggable
PersistenceRepositoryinterface for custom storage backends. onAuthExpiredcallback for token-refresh integration.onLoghook for routing internal events to Sentry / Crashlytics.- Multiple independent queues via
boxNameparameter.
