Fix backup loop, phone count invariant, and auto-start behaviour
- LocalPhotoIndex: add count(filter:) so fast-path reconcile uses the filtered asset count as phoneTotal instead of the unfiltered totalCount; fixes the broken alreadySafe + needBackup == phoneTotal invariant - BackupStatusService: fast-path uses count(filter:) instead of totalCount; writeManifest falls back to NASManifestCache when NAS download fails so accumulated history is never silently discarded on a flaky connection - ConnectionStore: autoBackupEnabled defaults false; new autoBackupOnOpen (default false) separates LAN-join auto-backup from app-open auto-backup - AutoBackupCoordinator: onActive(lanTriggered:) tracks trigger source; handleReconciliationComplete gates on autoBackupEnabled for LAN joins and autoBackupOnOpen for app-open — prevents backup starting on every launch - SettingsView: split auto-backup into two toggles with accurate descriptions Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -29,6 +29,8 @@ final class AutoBackupCoordinator: ObservableObject {
|
||||
@Published private(set) var phase: Phase = .idle
|
||||
@Published private(set) var pendingAtDisconnect: Int = 0
|
||||
|
||||
private var lastTriggerWasLAN = false
|
||||
|
||||
private let engine = BackupEngine.shared
|
||||
private let statusService = BackupStatusService.shared
|
||||
private let store = ConnectionStore.shared
|
||||
@@ -68,14 +70,12 @@ final class AutoBackupCoordinator: ObservableObject {
|
||||
|
||||
/// Call when the app becomes active: on first launch, foreground resume, or session unlock.
|
||||
/// Safe to call repeatedly — guards prevent redundant reconciliation.
|
||||
func onActive() {
|
||||
func onActive(lanTriggered: Bool = false) {
|
||||
lastTriggerWasLAN = lanTriggered
|
||||
guard !engine.job.status.isActive else { return }
|
||||
guard store.savedConnection != nil else { return }
|
||||
guard phase == .idle else { return } // Don't interrupt an in-progress check
|
||||
phase = .checking
|
||||
// force: false — BackupStatusService's 30-second debounce prevents expensive
|
||||
// rescans on rapid foreground/background cycles. The cached snapshot is shown
|
||||
// immediately; reconciliation only runs when truly stale.
|
||||
statusService.refresh(force: false)
|
||||
}
|
||||
|
||||
@@ -105,9 +105,16 @@ final class AutoBackupCoordinator: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-backup setting is off — show count but don't start
|
||||
guard store.autoBackupEnabled else {
|
||||
// Gate auto-backup on trigger source:
|
||||
// LAN join → autoBackupEnabled (lanTriggerEnabled guards the SSID check in LANMonitor)
|
||||
// App open → autoBackupOnOpen (off by default; requires explicit opt-in)
|
||||
let autoAllowed = lastTriggerWasLAN
|
||||
? store.autoBackupEnabled
|
||||
: store.autoBackupOnOpen
|
||||
|
||||
guard autoAllowed else {
|
||||
phase = .idle
|
||||
log.info("Auto-backup gated — trigger=\(self.lastTriggerWasLAN ? "LAN" : "appOpen") enabled=\(autoAllowed)")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,8 +123,8 @@ final class AutoBackupCoordinator: ObservableObject {
|
||||
|
||||
private func handleNASReachable() {
|
||||
guard case .disconnected = phase else { return }
|
||||
log.info("NAS reachable again — rescheduling auto-check")
|
||||
onActive()
|
||||
log.info("NAS reachable again — rescheduling auto-check (LAN trigger)")
|
||||
onActive(lanTriggered: true)
|
||||
}
|
||||
|
||||
// MARK: — Auto-backup trigger
|
||||
|
||||
Reference in New Issue
Block a user