@@ -81,32 +81,35 @@ class PersistenceController {
8181 // MARK: - Backup Management
8282
8383 func createBackup( ) async throws -> URL {
84- // Create timestamped backup file
85- let timestamp = ISO8601DateFormatter ( ) . string ( from: Date ( ) )
86- let backupURL = backupDirectoryURL. appendingPathComponent ( " Impulso_ \( timestamp) .backup " )
87-
88- // Ensure backup directory exists
89- try FileManager . default. createDirectory (
90- at: backupDirectoryURL,
91- withIntermediateDirectories: true ,
92- attributes: nil
93- )
94-
95- // Save the context to ensure all changes are persisted
96- let context = container. viewContext
97- if context. hasChanges {
98- try context. save ( )
99- }
100-
101- // Get the store URL
102- guard let storeURL = container. persistentStoreDescriptions. first? . url else {
103- throw BackupError . exportFailed
84+ return try await withCheckedThrowingContinuation { continuation in
85+ let context = container. newBackgroundContext ( )
86+
87+ context. performAndWait {
88+ do {
89+ let timestamp = ISO8601DateFormatter ( ) . string ( from: Date ( ) )
90+ let backupURL = backupDirectoryURL. appendingPathComponent ( " Impulso_ \( timestamp) .backup " )
91+
92+ try FileManager . default. createDirectory (
93+ at: backupDirectoryURL,
94+ withIntermediateDirectories: true ,
95+ attributes: nil
96+ )
97+
98+ if context. hasChanges {
99+ try context. save ( )
100+ }
101+
102+ guard let storeURL = container. persistentStoreDescriptions. first? . url else {
103+ throw BackupError . exportFailed
104+ }
105+
106+ try FileManager . default. copyItem ( at: storeURL, to: backupURL)
107+ continuation. resume ( returning: backupURL)
108+ } catch {
109+ continuation. resume ( throwing: error)
110+ }
111+ }
104112 }
105-
106- // Copy the store file to backup location
107- try FileManager . default. copyItem ( at: storeURL, to: backupURL)
108-
109- return backupURL
110113 }
111114
112115 func restoreFromBackup( at url: URL ) async throws {
@@ -158,41 +161,49 @@ class PersistenceController {
158161 func exportData( ) async throws -> URL {
159162 let encoder = JSONEncoder ( )
160163 encoder. dateEncodingStrategy = . iso8601
161- encoder. outputFormatting = . prettyPrinted
164+ encoder. outputFormatting = [ . prettyPrinted, . sortedKeys ]
162165
163166 let fetchRequest : NSFetchRequest < ImpulsoTask > = ImpulsoTask . fetchRequest ( )
164-
165167 let tasks = try container. viewContext. fetch ( fetchRequest)
166168 let taskData = try encoder. encode ( tasks. map ( TaskData . init) )
167169
168- let exportURL = FileManager . default. temporaryDirectory
169- . appendingPathComponent ( " Impulso_Export_ \( Date ( ) . timeIntervalSince1970) .json " )
170+ // Create a unique filename with safe characters
171+ let timestamp = Date ( ) . formatForFilename ( )
172+ let filename = " Impulso_Export_ \( timestamp) .json "
173+
174+ // Use the documents directory instead of temporary
175+ let documentsURL = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) [ 0 ]
176+ let exportURL = documentsURL. appendingPathComponent ( filename)
170177
171- try taskData. write ( to: exportURL)
178+ try taskData. write ( to: exportURL, options : . atomic )
172179 return exportURL
173180 }
174181
175182 func importData( from url: URL ) async throws {
183+ guard FileManager . default. fileExists ( atPath: url. path) else {
184+ throw BackupError . fileNotFound
185+ }
186+
176187 let data = try Data ( contentsOf: url)
177188 let decoder = JSONDecoder ( )
178189 decoder. dateDecodingStrategy = . iso8601
179190
180191 let taskData = try decoder. decode ( [ TaskData ] . self, from: data)
181192
182- // Create new background context for import
183193 let importContext = container. newBackgroundContext ( )
184-
185194 try await importContext. perform {
186- // Create new tasks from imported data
195+ // Clear existing tasks first
196+ let fetchRequest : NSFetchRequest < NSFetchRequestResult > = ImpulsoTask . fetchRequest ( )
197+ let batchDelete = NSBatchDeleteRequest ( fetchRequest: fetchRequest)
198+ try importContext. execute ( batchDelete)
199+
200+ // Import new tasks
187201 for taskInfo in taskData {
188202 let newTask = ImpulsoTask ( context: importContext)
189203 newTask. update ( from: taskInfo)
190204 }
191205
192- // Save imported tasks
193- if importContext. hasChanges {
194- try importContext. save ( )
195- }
206+ try importContext. save ( )
196207 }
197208 }
198209
0 commit comments