Integrate IndieBackup for snapshot backup and restore

Adds a local ZIP backup/restore of the iCloud document tree via the
IndieBackup package, surfaced in Settings with retention controls. A
restore suspends the sync observer, mirrors the files, then rebuilds the
SwiftData cache; opening a shared .workoutsbackup file restores it. The
engine exposes the container Documents root and a restore lifecycle
(isRestoring guards a concurrent connect), and the backup file type is
registered for open-in-place.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
2026-07-08 08:03:30 -04:00
parent df3eac9d5f
commit 653cc65e0e
7 changed files with 190 additions and 1 deletions
+10 -1
View File
@@ -1,6 +1,7 @@
import Foundation
import Observation
import SwiftData
import IndieBackup
/// Composition root for the iOS app. Owns the SwiftData cache container and the
/// iCloud sync engine, and drives the one-shot launch sequence. Injected into the
@@ -10,6 +11,10 @@ import SwiftData
final class AppServices {
let container: ModelContainer
let syncEngine: SyncEngine
/// Local ZIP backup/restore of the iCloud document tree (`backupRoot` = the
/// container's `Documents/`). Restores rebuild the SwiftData cache via
/// `SyncEngine.rebuildCache()`; see `AppBackupConfiguration`.
let backupController: BackupController
let watchBridge: PhoneConnectivityBridge
let workoutLauncher = WorkoutLauncher()
let workoutHealthDeleter = WorkoutHealthDeleter()
@@ -26,7 +31,11 @@ final class AppServices {
init() {
let container = WorkoutsModelContainer.make()
self.container = container
self.syncEngine = SyncEngine(container: container)
let syncEngine = SyncEngine(container: container)
self.syncEngine = syncEngine
self.backupController = BackupController(
configuration: AppBackupConfiguration(syncEngine: syncEngine)
)
let liveRunState = LiveRunState()
self.liveRunState = liveRunState
self.watchBridge = PhoneConnectivityBridge(container: container, syncEngine: syncEngine, liveRunState: liveRunState)