@@ -20,10 +20,8 @@ import Combine
2020import os. log
2121final class RCHPersistentContainer : NSPersistentContainer , @unchecked Sendable {
2222
23- /// Observe this bool to discover when initial restore has completed.
24- ///
25- /// WIll never revert back to false.
26- @Published var loaded : Bool = false
23+ /// Observe this state to discover when initial restore has completed.
24+ @Published var state : State = . loading
2725
2826 init ( storage: Storage ) {
2927 os_log ( " Initializing Core Data CommunicationPersistentContainer in storage mode: %{public}@ " , log: . communicationHub, type: . debug, storage. rawValue)
@@ -60,36 +58,49 @@ final class RCHPersistentContainer: NSPersistentContainer, @unchecked Sendable {
6058 }
6159
6260 private func configureWithSqlite( ) {
61+ loadPersistentStoresWithRetry ( isRetry: false )
62+ }
63+
64+ private func loadPersistentStoresWithRetry( isRetry: Bool ) {
6365 loadPersistentStores { description, error in
64- defer {
65- self . loaded = true
66- }
67-
6866 if let error = error {
69- os_log ( " Error loading Core Data persistent store: %{public}@ " , log: . communicationHub, type: . error, error. localizedDescription)
70-
71- // Get the URL from the description
72- if let url = description. url {
73- do {
74- let coordinator = self . persistentStoreCoordinator
75-
76- // Destroy the persistent store
77- try coordinator. destroyPersistentStore ( at: url, ofType: NSSQLiteStoreType, options: nil )
78- os_log ( " Successfully destroyed Core Data persistent store at: %{public}@ " , log: . communicationHub, type: . info, url. path)
79-
80- // Recreate the persistent store
81- try coordinator. addPersistentStore ( ofType: NSSQLiteStoreType, configurationName: nil , at: url, options: nil )
82- os_log ( " Successfully recreated Core Data persistent store at: %{public}@ " , log: . communicationHub, type: . info, url. path)
83-
84- } catch {
85- os_log ( " Failed to reset Core Data persistent store: %{public}@ " , log: . communicationHub, type: . error, error. localizedDescription)
86- fatalError ( " Unable to recover from Core Data store failure " )
87- }
88- } else {
89- fatalError ( " Failed to get URL from Core Data persistent store description " )
67+ os_log ( " Error loading Core Data persistent store%{public}@: %{public}@ " , log: . communicationHub, type: . error, isRetry ? " (retry) " : " " , error. localizedDescription)
68+
69+ // If this was already a retry attempt, fall back to in-memory storage
70+ guard !isRetry else {
71+ os_log ( " Retry attempt failed, falling back to in-memory storage " , log: . communicationHub, type: . error)
72+ self . fallbackToInMemoryStorage ( )
73+ return
74+ }
75+
76+ // Get the URL from the description in order to attempt dropping and recreating the DB.
77+ guard let storeURL = description. url else {
78+ os_log ( " loadPersistentStores() error state occurred, but unable to obtain store URLs to attempt dropping DB, falling back to in-memory storage " , log: . communicationHub, type: . error)
79+ self . fallbackToInMemoryStorage ( )
80+ return
9081 }
82+
83+ do {
84+ let coordinator = self . persistentStoreCoordinator
85+
86+ // Destroy the persistent store
87+ try coordinator. destroyPersistentStore ( at: storeURL, ofType: NSSQLiteStoreType, options: nil )
88+ os_log ( " Successfully destroyed Core Data persistent store at: %{public}@ " , log: . communicationHub, type: . info, storeURL. path)
89+
90+ // Attempt to load the persistent stores one more time
91+ os_log ( " Attempting to retry loading persistent stores after recreation " , log: . communicationHub, type: . info)
92+ self . loadPersistentStoresWithRetry ( isRetry: true )
93+
94+ } catch {
95+ os_log ( " Failed to reset Core Data persistent store: %{public}@ " , log: . communicationHub, type: . error, error. localizedDescription)
96+ os_log ( " Falling back to in-memory storage after failed store recreation " , log: . communicationHub, type: . error)
97+ self . fallbackToInMemoryStorage ( )
98+
99+ }
100+
91101 } else {
92- os_log ( " Successfully loaded Core Data Communication Hub persistent stores " , log: . communicationHub, type: . info)
102+ os_log ( " Successfully loaded Core Data Communication Hub persistent stores%{public}@ " , log: . communicationHub, type: . info, isRetry ? " (after retry) " : " " )
103+ self . state = . loaded
93104 }
94105 }
95106 }
@@ -102,14 +113,44 @@ final class RCHPersistentContainer: NSPersistentContainer, @unchecked Sendable {
102113
103114 self . loadPersistentStores { description, error in
104115 defer {
105- self . loaded = true
116+ self . state = . loaded
106117 }
107118
108119 if let error = error {
109120 fatalError ( " Failed to create in-memory persistent store: \( error) " )
110121 }
111122 }
112123 }
124+
125+ private func fallbackToInMemoryStorage( ) {
126+ os_log ( " Configuring fallback to in-memory storage due to persistent store failures " , log: . communicationHub, type: . info)
127+
128+ // Clean up any existing persistent stores
129+ let coordinator = self . persistentStoreCoordinator
130+ for store in coordinator. persistentStores {
131+ do {
132+ try coordinator. remove ( store)
133+ } catch {
134+ os_log ( " Failed to remove existing persistent store during fallback: %{public}@ " , log: . communicationHub, type: . error, error. localizedDescription)
135+ }
136+ }
137+
138+ // Configure for in-memory storage
139+ let description = NSPersistentStoreDescription ( )
140+ description. type = NSInMemoryStoreType
141+ self . persistentStoreDescriptions = [ description]
142+
143+ // Load the in-memory store
144+ self . loadPersistentStores { description, error in
145+ if let error = error {
146+ os_log ( " Critical error: Failed to create in-memory fallback store: %{public}@ " , log: . communicationHub, type: . fault, error. localizedDescription)
147+ self . state = . failed
148+ } else {
149+ os_log ( " Successfully configured in-memory fallback storage " , log: . communicationHub, type: . info)
150+ self . state = . loaded
151+ }
152+ }
153+ }
113154
114155 /// Drop the database and reset the persistent container. Note that will leave the store in a dropped state, and the app (and Rover SDK) should be restarted afterward.
115156 func reset( ) {
@@ -135,8 +176,15 @@ final class RCHPersistentContainer: NSPersistentContainer, @unchecked Sendable {
135176 /// Using in-memory, meant for SwiftUI previews and other testing scenarios.
136177 case inMemory
137178 }
179+
180+ enum State : String {
181+ case loading
182+ case loaded
183+ case failed
184+ }
138185}
139186
187+
140188// MARK: - Badge Count Extension
141189extension RCHPersistentContainer {
142190 /// Computes the current badge count based on unread posts.
0 commit comments