fix: use NAS directory filenames for already-safe matching when manifest is sparse
When Kisani's manifest has 0–1 entries but the NAS directory contains thousands of files uploaded by other tools, reconcile previously showed Already Safe = 1 because countSafe only matched against the tiny manifest. The validity check in buildManifestIndex now: - Stores the filtered NAS filenames in NASManifestCache (directoryFilenames field) - Returns a ManifestIndex(nasListing:) for filename-based matching against all NAS files The fast-path reconcile now: - Detects a sparse manifest (entries ≤ 1, dirCount > 1) - Rebuilds ManifestIndex(nasFilenames:) from the cached filenames for O(1) filename matching - Falls back to full NAS reconcile if cached filenames aren't available yet Adds ManifestIndex.init(nasFilenames:) for fast-path reconstruction from cached strings. Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -159,13 +159,27 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
let manifestCacheIsStale = await NASManifestCache.shared.isStale
|
||||
let localIndexCount = await LocalPhotoIndex.shared.totalCount
|
||||
|
||||
if !manifestCacheIsStale, let cached = await NASManifestCache.shared.manifest, localIndexCount > 0 {
|
||||
let index = await Task.detached(priority: .utility) {
|
||||
ManifestIndex(manifest: cached)
|
||||
}.value
|
||||
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.
|
||||
guard let filenames = await NASManifestCache.shared.directoryFilenames else {
|
||||
log.info("Reconcile (cached): sparse manifest, no cached filenames — falling to full reconcile")
|
||||
break fastPath
|
||||
}
|
||||
index = await Task.detached(priority: .utility) {
|
||||
ManifestIndex(nasFilenames: filenames)
|
||||
}.value
|
||||
} else {
|
||||
index = await Task.detached(priority: .utility) {
|
||||
ManifestIndex(manifest: cached)
|
||||
}.value
|
||||
}
|
||||
let safe = await LocalPhotoIndex.shared.countSafe(against: index, filter: filter)
|
||||
let phoneTotal = await LocalPhotoIndex.shared.count(filter: filter)
|
||||
let dirCount = await NASManifestCache.shared.directoryCount ?? 0
|
||||
let nasTotal = max(index.totalCount, dirCount)
|
||||
var updated = BackupStatusSnapshot()
|
||||
updated.phoneTotal = phoneTotal
|
||||
@@ -295,17 +309,21 @@ final class BackupStatusService: NSObject, ObservableObject {
|
||||
Task { await NASManifestCache.shared.update(manifest) }
|
||||
|
||||
// Validity check: if the manifest has ≤ 1 entries it may be corrupt or newly
|
||||
// created. Scan the directory to get the real file count for the NAS Archive
|
||||
// display — but do NOT add directory files as orphan manifest entries.
|
||||
// created. Scan the directory to get the real NAS contents. When the directory
|
||||
// is larger than the manifest, return a filename-based index so countSafe
|
||||
// matches against the actual NAS files, not just the sparse manifest entries.
|
||||
if index.totalCount <= 1 {
|
||||
let items = (try? await Self.withTimeout(12) { try await transfer.listDirectory(at: conn.remotePath) }) ?? []
|
||||
let dirCount = items.filter {
|
||||
let filteredItems = items.filter {
|
||||
!$0.isDirectory && !$0.name.hasPrefix(".") && $0.name != BackupManifest.remoteFilename
|
||||
}.count
|
||||
log.info("buildManifestIndex: validity — manifest=\(index.totalCount) dir=\(dirCount) — using max")
|
||||
}
|
||||
let dirCount = filteredItems.count
|
||||
log.info("buildManifestIndex: validity — manifest=\(index.totalCount) dir=\(dirCount) — filename matching")
|
||||
if dirCount > index.totalCount {
|
||||
await NASManifestCache.shared.setDirectoryCount(dirCount)
|
||||
return ManifestIndex(manifest: manifest, overrideTotalCount: dirCount)
|
||||
await NASManifestCache.shared.setDirectoryFilenames(filteredItems.map { $0.name })
|
||||
return await Task.detached(priority: .utility) {
|
||||
ManifestIndex(nasListing: items)
|
||||
}.value
|
||||
}
|
||||
}
|
||||
return index
|
||||
|
||||
Reference in New Issue
Block a user