Fix Already Safe = 0: hybrid ManifestIndex preserves localIdentifiers + NAS filenames
The root cause: buildManifestIndex was discarding manifest localIdentifiers and switching to pure filename-only matching whenever dirCount > manifest.totalCount (which is always true when the NAS has files from other tools). If filenames didn't match exactly, Already Safe dropped to 0 on every stale-cache reconcile. Fix: introduce ManifestIndex(manifest:nasFilenames:totalCount:) — a hybrid that keeps Kisani's localIdentifiers as primary keys AND adds all NAS directory filenames for filename-fallback. Used in: - buildManifestIndex (full reconcile) - fast path (when dirCount > cached.entries.count, previously gated at <= 1) - BackupEngine step 3b (so "Back up again" only uploads genuinely missing files) Also simplify countSafe / pendingIDs / step-4 filter to always try filename fallback (removes the isFilenameOnly gate that blocked matches when the index had IDs). Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -162,16 +162,17 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
fastPath: if !manifestCacheIsStale, let cached = await NASManifestCache.shared.manifest, localIndexCount > 0 {
|
||||
let dirCount = await NASManifestCache.shared.directoryCount ?? 0
|
||||
let index: ManifestIndex
|
||||
if cached.entries.count <= 1 && dirCount > 1 {
|
||||
// Manifest is sparse vs. actual NAS directory. Use cached filenames for
|
||||
// filename-based matching so countSafe reflects real NAS coverage.
|
||||
// If filenames aren't cached yet, fall through to a full NAS reconcile.
|
||||
if dirCount > cached.entries.count {
|
||||
// NAS has more files than the manifest tracks. Build a hybrid index that keeps
|
||||
// manifest localIdentifiers (for Kisani uploads) and adds all cached NAS
|
||||
// filenames as a filename-fallback. If filenames aren't cached yet, fall
|
||||
// through to a full NAS reconcile so they get populated.
|
||||
guard let filenames = await NASManifestCache.shared.directoryFilenames else {
|
||||
log.info("Reconcile (cached): sparse manifest, no cached filenames — falling to full reconcile")
|
||||
log.info("Reconcile (cached): NAS dir(\(dirCount)) > manifest(\(cached.entries.count)) — no cached filenames, falling to full reconcile")
|
||||
break fastPath
|
||||
}
|
||||
index = await Task.detached(priority: .utility) {
|
||||
ManifestIndex(nasFilenames: filenames)
|
||||
ManifestIndex(manifest: cached, nasFilenames: filenames, totalCount: dirCount)
|
||||
}.value
|
||||
} else {
|
||||
index = await Task.detached(priority: .utility) {
|
||||
@@ -320,9 +321,13 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
let dirCount = filteredItems.count
|
||||
log.info("buildManifestIndex: dir=\(dirCount) manifest=\(index.totalCount)")
|
||||
if dirCount > index.totalCount {
|
||||
await NASManifestCache.shared.setDirectoryFilenames(filteredItems.map { $0.name })
|
||||
// NAS has more files than manifest: keep manifest localIdentifiers
|
||||
// for Kisani-tracked uploads and add all NAS filenames as fallback.
|
||||
// This prevents discarding accumulated localIdentifier history.
|
||||
let nasNames = filteredItems.map { $0.name }
|
||||
await NASManifestCache.shared.setDirectoryFilenames(nasNames)
|
||||
return await Task.detached(priority: .utility) {
|
||||
ManifestIndex(nasListing: items)
|
||||
ManifestIndex(manifest: manifest, nasFilenames: nasNames, totalCount: dirCount)
|
||||
}.value
|
||||
} else {
|
||||
await NASManifestCache.shared.setDirectoryCount(dirCount)
|
||||
@@ -371,12 +376,9 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
count += 1
|
||||
} else {
|
||||
let resources = PHAssetResource.assetResources(for: asset)
|
||||
if let name = resources.first?.originalFilename {
|
||||
if index.isFilenameOnly && index.matches(filename: name) {
|
||||
count += 1
|
||||
} else if index.matchesUnclaimed(filename: name) {
|
||||
count += 1
|
||||
}
|
||||
if let name = resources.first?.originalFilename,
|
||||
index.matches(filename: name) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user