fix: manifest cache not updated after backup causes NAS Archive = 1 and backup loop
- writeManifest now updates NASManifestCache after every successful NAS write; previously the fast-path reconcile would read the pre-backup cache entry (e.g. 1 entry) and overwrite nasArchiveTotal / alreadySafe with stale values - BackupEngine seeds NASManifestCache when it loads the manifest so the cache is always at least as fresh as what the engine sees at backup-start time - Remove snapshot.nasArchiveTotal += uploaded from refreshAfterBackup; the correct total comes from the merged manifest written to NAS, not an additive delta from a potentially-wrong baseline - Fix JSONDecoder() → JSONDecoder.kisani throughout manifest load paths so ISO8601 dates decode correctly instead of silently failing and returning nil - Add retry with exponential backoff (max 3 attempts, 2 s / 4 s intervals) to the upload loop; permanently-failed items are now marked .failed only after all retries are exhausted Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -68,7 +68,8 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
func refreshAfterBackup(entries: [ManifestEntry], connection: NASConnection) {
|
||||
let uploaded = entries.count
|
||||
snapshot.alreadySafe = min(snapshot.phoneTotal, snapshot.alreadySafe + uploaded)
|
||||
snapshot.nasArchiveTotal += uploaded
|
||||
// nasArchiveTotal is NOT updated optimistically — writeManifest will update
|
||||
// NASManifestCache with the full merged count, and refresh(force: true) reads it.
|
||||
saveSnapshot()
|
||||
log.info("Post-backup optimistic update: +\(uploaded) safe")
|
||||
Task {
|
||||
@@ -234,7 +235,7 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
let data = try await transfer.downloadData(at: path)
|
||||
// Decode and build index off main actor — JSONDecoder is not cheap for large manifests.
|
||||
let decoded: (ManifestIndex, BackupManifest)? = await Task.detached(priority: .utility) {
|
||||
guard let manifest = try? JSONDecoder().decode(BackupManifest.self, from: data)
|
||||
guard let manifest = try? JSONDecoder.kisani.decode(BackupManifest.self, from: data)
|
||||
else { return nil }
|
||||
return (ManifestIndex(manifest: manifest), manifest)
|
||||
}.value
|
||||
@@ -332,23 +333,29 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
let existingData = try? await transfer.downloadData(at: manifestPath)
|
||||
|
||||
// Merge + JSON encode off main actor — can be slow for large manifests.
|
||||
let encoded: Data? = await Task.detached(priority: .utility) {
|
||||
let result: (data: Data, manifest: BackupManifest)? = await Task.detached(priority: .utility) {
|
||||
var manifest: BackupManifest
|
||||
if let data = existingData,
|
||||
let existing = try? JSONDecoder().decode(BackupManifest.self, from: data) {
|
||||
let existing = try? JSONDecoder.kisani.decode(BackupManifest.self, from: data) {
|
||||
manifest = existing
|
||||
} else {
|
||||
manifest = BackupManifest()
|
||||
}
|
||||
manifest.merge(entries: entries)
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .iso8601
|
||||
return try? encoder.encode(manifest)
|
||||
guard let encoded = try? JSONEncoder.kisani.encode(manifest) else { return nil }
|
||||
return (encoded, manifest)
|
||||
}.value
|
||||
guard let encoded else { return }
|
||||
|
||||
guard let (encoded, mergedManifest) = result else { return }
|
||||
|
||||
try await transfer.writeData(encoded, to: manifestPath)
|
||||
log.info("Manifest written — \(entries.count) new entries")
|
||||
|
||||
// Update local cache immediately — the next reconcile's fast path will read
|
||||
// this fresh cache instead of re-downloading from NAS, preventing the stale-
|
||||
// cache bug where nasArchiveTotal resets to the pre-backup count.
|
||||
await NASManifestCache.shared.update(mergedManifest)
|
||||
lastManifest = mergedManifest
|
||||
log.info("Manifest written — \(entries.count) new entries, total \(mergedManifest.entries.count)")
|
||||
} catch {
|
||||
log.error("Manifest write failed (non-fatal): \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user