fix: NAS operation timeouts, fast-path warm-up, 30-min stale threshold

Three changes that together eliminate the "Checking…" stall and excessive NAS
rescanning without requiring a database migration:

NASManifestCache: extend stale threshold from 5 min to 30 min. directoryCount
is now updated incrementally after every upload, and PHPhotoLibraryChangeObserver
keeps LocalPhotoIndex current — a full NAS roundtrip every 5 min was redundant
and forced the expensive reconcile path far more often than necessary.

BackupStatusService: add withTimeout(_:work:) using a racing ThrowingTaskGroup
so NAS operations (SMB connect, manifest download, directory listing) fail fast
instead of hanging indefinitely when the NAS is slow or unreachable. Timeouts:
15s for connect, 12s for manifest download, 12s for directory listings. On
timeout, BackupError.timeout is caught separately to preserve cached counts
instead of marking connectionState = .offline.

BackupStatusService: call LocalPhotoIndex.shared.loadOrBuild() at the start of
performRefresh before checking localIndexCount. Without this, AppDelegate's
concurrent loadOrBuild() task races with the first onActive() trigger; if it
loses, localIndexCount == 0 and the fast path is bypassed, forcing a full NAS
roundtrip on every cold launch.

Co-Authored-By: Kutesir <kutesir@provoc.ug>
Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
Robin Kutesa
2026-05-18 20:41:49 +03:00
parent 788262502d
commit 0e65a548f2
2 changed files with 52 additions and 9 deletions

View File

@@ -14,7 +14,10 @@ actor NASManifestCache {
private var cached: CachedEntry?
private let diskURL: URL
private static let staleInterval: TimeInterval = 5 * 60
// 30-minute window: directoryCount is updated incrementally after every upload,
// and PHPhotoLibraryChangeObserver keeps LocalPhotoIndex current. A full NAS
// roundtrip every 5 minutes was unnecessary and caused "Checking" stalls.
private static let staleInterval: TimeInterval = 30 * 60
private struct CachedEntry: Codable {
var manifest: BackupManifest