Fix NAS Archive reset and Already Safe regression

BackupEngine — manifest loading never poisons NASManifestCache:
  Before: if the NAS manifest download failed for any reason,
  BackupManifest() (0 entries) was written to NASManifestCache.
  Every subsequent fast-path reconcile read the poisoned cache and
  set nasArchiveTotal = 0, making every asset appear as needing backup.
  Checkpoint writes then set the cache to 1 entry (first upload), which
  is why NAS Archive flipped from 7422 to 1 when starting a backup.

  After: NAS > cache > empty. If NAS download fails or returns an empty
  manifest, the existing cache is used as the authoritative base and is
  never overwritten. Only a non-empty NAS manifest replaces the cache.

BackupView — live counters stay on persistent totals:
  - nasArchiveDisplayCount: snapshot.nasArchiveTotal + uploadedFiles during
    active backup so the NAS Archive card grows as uploads are confirmed
    instead of jumping to the session count
  - alreadySafeDisplayCount: adds both uploadedFiles AND skippedFiles —
    skipped means the file was already on NAS (fileExists=true) but not
    yet in the manifest, so it is safe and should be counted immediately
  - notBackedUpDisplayCount already derived from alreadySafeDisplayCount
    so it correctly shrinks as uploads and skips are confirmed

Co-Authored-By: Kutesir <kutesir@provoc.ug>
Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
Robin Kutesa
2026-05-18 17:24:56 +03:00
parent ae22d7a739
commit 00e4ecc506
2 changed files with 44 additions and 12 deletions

View File

@@ -428,14 +428,29 @@ struct BackupView: View {
// Invariant: alreadySafe + needBackup == phoneTotal always.
// During active backup show optimistic count (only newly uploaded, not skipped
// skipped files are already counted inside snapshot.alreadySafe from the last reconcile).
// Live NAS Archive: grows by 1 with each confirmed upload this session.
// Skipped files (already on NAS) are NOT added if nasArchiveTotal came from
// a directory scan they're already counted; if from manifest they weren't tracked
// but they're present on disk regardless.
private var nasArchiveDisplayCount: Int {
let base = statusService.snapshot.nasArchiveTotal
if engine.job.status.isActive {
return base + engine.job.uploadedFiles
}
return base
}
private var alreadySafeDisplayCount: Int {
let total = statusService.snapshot.phoneTotal
guard total > 0 else { return 0 }
let base = statusService.snapshot.alreadySafe
if engine.job.status.isActive {
return min(base + engine.job.uploadedFiles, total)
// Uploaded = newly confirmed on NAS this session.
// Skipped = fileExists returned true already on NAS but wasn't in manifest.
// Both are now confirmed safe; count them both optimistically.
return min(base + engine.job.uploadedFiles + engine.job.skippedFiles, total)
}
return min(base, total) // enforce invariant at all times
return min(base, total)
}
private var notBackedUpDisplayCount: Int {
@@ -451,7 +466,7 @@ struct BackupView: View {
VStack(spacing: 7) {
HStack(spacing: 0) {
compactStat(
"\(statusService.snapshot.nasArchiveTotal)",
"\(nasArchiveDisplayCount)",
label: "NAS Archive",
color: AppTheme.interactive
)