fix: NAS Archive always shows real directory count, not manifest entry count
- NASManifestCache: add directoryCount field (Int?) stored independently from manifest.entries.count; add setDirectoryCount(_:) and addToDirectoryCount(_:); update(_:) now preserves existing directoryCount so manifest refreshes never reset the real folder count to zero - BackupEngine step 3: switch from "NAS if non-empty, else cache" to max(NAS, cache) by entry count — a 1-entry NAS manifest no longer overwrites a 264-entry cache, preventing history loss and the Already Safe = 1 regression - BackupEngine post-upload: call addToDirectoryCount(uploaded) so NAS Archive increments correctly (7422 → 7423) without requiring a full directory rescan - BackupStatusService.buildManifestIndex: call setDirectoryCount(dirCount) in both the validity-check path (manifest ≤ 1 entry) and the bootstrap path so the real folder count survives across reconcile cycles - BackupStatusService.writeManifest: same max(NAS, cache) base-selection logic to fix Already Safe = 1 after backup ends (merge base was 1-entry NAS instead of 264-entry cache) - BackupStatusService.reconcile (fast + full path): use max(index.totalCount, directoryCount) for nasArchiveTotal so Gallery and stats strip reflect the real NAS folder count, not just Kisani manifest entries Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -104,19 +104,22 @@ final class BackupEngine: ObservableObject {
|
||||
return try? JSONDecoder.kisani.decode(BackupManifest.self, from: data)
|
||||
}.value
|
||||
|
||||
// Authoritative base precedence: NAS → local cache → empty.
|
||||
// CRITICAL: never overwrite a populated cache with an empty/missing NAS result.
|
||||
// If the download fails or the manifest file doesn't exist yet, writing
|
||||
// BackupManifest() to NASManifestCache destroys accumulated history and
|
||||
// causes NAS Archive to drop to 0, making every asset appear as needing backup.
|
||||
// Use the manifest with more entries as the authoritative base.
|
||||
// CRITICAL: if the NAS manifest has fewer entries than the local cache (e.g. 1 vs 264),
|
||||
// the cache preserves accumulated history and must win. Overwriting the cache with a
|
||||
// smaller NAS manifest destroys history and causes every asset to re-upload.
|
||||
let cachedManifest = await NASManifestCache.shared.manifest
|
||||
let cacheCount = cachedManifest?.entries.count ?? 0
|
||||
let nasCount = nasManifest?.entries.count ?? 0
|
||||
|
||||
let baseManifest: BackupManifest
|
||||
if let nas = nasManifest, !nas.entries.isEmpty {
|
||||
baseManifest = nas
|
||||
await NASManifestCache.shared.update(nas)
|
||||
logger.info("Manifest loaded from NAS — \(nas.entries.count) entries")
|
||||
} else if let cached = await NASManifestCache.shared.manifest, !cached.entries.isEmpty {
|
||||
baseManifest = cached
|
||||
logger.info("Manifest from cache — \(cached.entries.count) entries (NAS: \(nasManifest == nil ? "download failed" : "file empty"))")
|
||||
if nasCount > 0, nasCount >= cacheCount {
|
||||
baseManifest = nasManifest!
|
||||
await NASManifestCache.shared.update(nasManifest!)
|
||||
logger.info("Manifest from NAS — \(nasCount) entries")
|
||||
} else if cacheCount > 0 {
|
||||
baseManifest = cachedManifest!
|
||||
logger.info("Manifest from cache — \(cacheCount) entries (NAS had \(nasCount))")
|
||||
} else {
|
||||
// Genuinely first backup — no manifest anywhere.
|
||||
baseManifest = nasManifest ?? BackupManifest()
|
||||
@@ -320,6 +323,11 @@ final class BackupEngine: ObservableObject {
|
||||
|
||||
signposter.endInterval("UploadLoop", spUpload, "\(uploaded) uploaded, \(failed) failed")
|
||||
|
||||
// Keep directoryCount current without a full rescan.
|
||||
if uploaded > 0 {
|
||||
await NASManifestCache.shared.addToDirectoryCount(uploaded)
|
||||
}
|
||||
|
||||
let duration = Date().timeIntervalSince(startDate)
|
||||
let result = BackupResult(
|
||||
uploadedCount: uploaded,
|
||||
|
||||
Reference in New Issue
Block a user