Parcourir la source

batch delete function for NSManagedObjects...wip

polscm32 il y a 2 ans
Parent
commit
ff2f11aab6

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -310,6 +310,7 @@
 		5825D15D2BD4058F00F36E9B /* Target+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825D12F2BD4058F00F36E9B /* Target+CoreDataProperties.swift */; };
 		5825D15E2BD4058F00F36E9B /* Protein+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825D1302BD4058F00F36E9B /* Protein+CoreDataClass.swift */; };
 		5825D15F2BD4058F00F36E9B /* Protein+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825D1312BD4058F00F36E9B /* Protein+CoreDataProperties.swift */; };
+		582FAE432C05102C00D1C13F /* CoreDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582FAE422C05102C00D1C13F /* CoreDataError.swift */; };
 		583684062BD178DB00070A60 /* GlucoseStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583684052BD178DB00070A60 /* GlucoseStored+helper.swift */; };
 		583684082BD195A700070A60 /* Determination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583684072BD195A700070A60 /* Determination.swift */; };
 		5837A5302BD2E3C700A5DC04 /* CarbEntryStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5837A52F2BD2E3C700A5DC04 /* CarbEntryStored+helper.swift */; };
@@ -936,6 +937,7 @@
 		5825D12F2BD4058F00F36E9B /* Target+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Target+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		5825D1302BD4058F00F36E9B /* Protein+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Protein+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
 		5825D1312BD4058F00F36E9B /* Protein+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Protein+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
+		582FAE422C05102C00D1C13F /* CoreDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataError.swift; sourceTree = "<group>"; };
 		583684052BD178DB00070A60 /* GlucoseStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GlucoseStored+helper.swift"; sourceTree = "<group>"; };
 		583684072BD195A700070A60 /* Determination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Determination.swift; sourceTree = "<group>"; };
 		5837A52F2BD2E3C700A5DC04 /* CarbEntryStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbEntryStored+helper.swift"; sourceTree = "<group>"; };
@@ -2233,6 +2235,7 @@
 				5887527B2BD986E1008B081D /* OpenAPSBattery.swift */,
 				581AC4382BE22ED10038760C /* JSONConverter.swift */,
 				BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */,
+				582FAE422C05102C00D1C13F /* CoreDataError.swift */,
 			);
 			path = Helper;
 			sourceTree = "<group>";
@@ -3005,6 +3008,7 @@
 				3811DE1825C9D40400A708ED /* Router.swift in Sources */,
 				5825D1462BD4058F00F36E9B /* Oref0Suggestion+CoreDataClass.swift in Sources */,
 				CE7950262998056D00FA576E /* CGMSetupView.swift in Sources */,
+				582FAE432C05102C00D1C13F /* CoreDataError.swift in Sources */,
 				38A0363B25ECF07E00FCBB52 /* GlucoseStorage.swift in Sources */,
 				190EBCC629FF138000BA767D /* StatConfigProvider.swift in Sources */,
 				38E98A2725F52C9300C0CED0 /* CollectionIssueReporter.swift in Sources */,

+ 1 - 1
FreeAPS/Sources/APS/APSManager.swift

@@ -155,7 +155,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
         // Cleanup
         Task {
-            await CoreDataStack.shared.cleanupPersistentHistory(before: Date.oneWeekAgo)
+            await CoreDataStack.shared.cleanupPersistentHistoryTokens(before: Date.oneWeekAgo)
         }
 
         // Update lastHistoryCleanupDate

+ 20 - 5
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -58,11 +58,8 @@ import Swinject
         )
         loadServices()
 
-        // Clear the persistentHistory every time the app starts
-        let coreDataStack = self.coreDataStack
-        Task {
-            await coreDataStack.cleanupPersistentHistory(before: Date.oneWeekAgo)
-        }
+        // Clear the persistentHistory and the NSManagedObjects that are older than 90 days every time the app starts
+        cleanupOldData()
     }
 
     var body: some Scene {
@@ -77,6 +74,24 @@ import Swinject
         }
     }
 
+    private func cleanupOldData() {
+        Task {
+            await coreDataStack.cleanupPersistentHistoryTokens(before: Date.oneWeekAgo)
+            try await purgeOldNSManagedObjects()
+        }
+    }
+
+    private func purgeOldNSManagedObjects() async throws {
+        try await coreDataStack.batchDeleteOlderThan(GlucoseStored.self, dateKey: "date", days: 90)
+        try await coreDataStack.batchDeleteOlderThan(PumpEventStored.self, dateKey: "timestamp", days: 90)
+        try await coreDataStack.batchDeleteOlderThan(OrefDetermination.self, dateKey: "deliverAt", days: 90)
+        try await coreDataStack.batchDeleteOlderThan(OpenAPS_Battery.self, dateKey: "date", days: 90)
+        try await coreDataStack.batchDeleteOlderThan(CarbEntryStored.self, dateKey: "date", days: 90)
+        try await coreDataStack.batchDeleteOlderThan(Forecast.self, dateKey: "date", days: 90)
+
+        // TODO: - Purge Data of other (future) entities as well
+    }
+
     private func handleURL(_ url: URL) {
         let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
 

+ 47 - 26
Model/CoreDataStack.swift

@@ -130,14 +130,14 @@ class CoreDataStack: ObservableObject {
 
     // Clean old Persistent History
     /// - Tag: clearHistory
-    func cleanupPersistentHistory(before date: Date) async {
+    func cleanupPersistentHistoryTokens(before date: Date) async {
         let taskContext = newTaskContext()
-        taskContext.name = "cleanPersistentHistoryContext"
+        taskContext.name = "cleanPersistentHistoryTokensContext"
 
         await taskContext.perform {
-            let deleteHistoryRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: date)
+            let deleteHistoryTokensRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: date)
             do {
-                try taskContext.execute(deleteHistoryRequest)
+                try taskContext.execute(deleteHistoryTokensRequest)
                 debugPrint("\(DebuggingIdentifiers.succeeded) Successfully deleted persistent history before \(date)")
             } catch {
                 debugPrint(
@@ -173,28 +173,49 @@ extension CoreDataStack {
 
     /// Asynchronously deletes records
     ///  - Tag: batchDelete
-//    func batchDelete<T: NSManagedObject>(_ objects: [T]) async throws {
-//        let objectIDs = objects.map(\.objectID)
-//        let taskContext = newTaskContext()
-//        // Add name and author to identify source of persistent history changes.
-//        taskContext.name = "deleteContext"
-//        taskContext.transactionAuthor = "batchDelete"
-//        debugPrint("Start deleting data from the store... \(DebuggingIdentifiers.inProgress)")
-//
-//        try await taskContext.perform {
-//            // Execute the batch delete.
-//            let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
-//            guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
-//                  let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
-//                  let success = batchDeleteResult.result as? Bool, success
-//            else {
-//                debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
-//                throw CoreDataError.batchDeleteError
-//            }
-//        }
-//
-//        debugPrint("Successfully deleted data. \(DebuggingIdentifiers.succeeded)")
-//    }
+    func batchDeleteOlderThan<T: NSManagedObject>(_ objectType: T.Type, dateKey: String, days: Int) async throws {
+        let taskContext = newTaskContext()
+        taskContext.name = "deleteContext"
+        taskContext.transactionAuthor = "batchDelete"
+
+        // Get the number of days we want to keep the data
+        let targetDate = Calendar.current.date(byAdding: .day, value: -days, to: Date())!
+
+        // Fetch all the objects that are older than the specified days
+        let fetchRequest = NSFetchRequest<NSManagedObjectID>(entityName: String(describing: objectType))
+        fetchRequest.predicate = NSPredicate(format: "%K < %@", dateKey, targetDate as NSDate)
+        fetchRequest.resultType = .managedObjectIDResultType
+
+        do {
+            // Execute the Fetch Request
+            let objectIDs = try await taskContext.perform {
+                try taskContext.fetch(fetchRequest)
+            }
+
+            // Guard check if there are NSManagedObjects older than 90 days
+            guard !objectIDs.isEmpty else {
+                debugPrint("No objects found older than \(days) days.")
+                return
+            }
+
+            // Execute the Batch Delete
+            try await taskContext.perform {
+                let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
+                guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
+                      let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
+                      let success = batchDeleteResult.result as? Bool, success
+                else {
+                    debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
+                    throw CoreDataError.batchDeleteError
+                }
+            }
+
+            debugPrint("Successfully deleted data older than \(days) days. \(DebuggingIdentifiers.succeeded)")
+        } catch {
+            debugPrint("Failed to fetch or delete data: \(error.localizedDescription) \(DebuggingIdentifiers.failed)")
+            throw CoreDataError.batchDeleteError
+        }
+    }
 }
 
 // MARK: - Fetch Requests

+ 35 - 0
Model/Helper/CoreDataError.swift

@@ -0,0 +1,35 @@
+import Foundation
+
+enum CoreDataError: Error {
+    case creationError
+    case batchInsertError
+    case batchDeleteError
+    case persistentHistoryChangeError
+    case unexpectedError(error: Error)
+    case fetchError
+}
+
+extension CoreDataError: LocalizedError {
+    var errorDescription: String? {
+        switch self {
+        case .creationError:
+            return NSLocalizedString("Failed to create a new object.", comment: "")
+        case .batchInsertError:
+            return NSLocalizedString("Failed to execute a batch insert request.", comment: "")
+        case .batchDeleteError:
+            return NSLocalizedString("Failed to execute a batch delete request.", comment: "")
+        case .persistentHistoryChangeError:
+            return NSLocalizedString("Failed to execute a persistent history change request.", comment: "")
+        case let .unexpectedError(error):
+            return NSLocalizedString("Received unexpected error. \(error.localizedDescription)", comment: "")
+        case .fetchError:
+            return NSLocalizedString("Failed to fetch object \(DebuggingIdentifiers.failed).", comment: "")
+        }
+    }
+}
+
+extension CoreDataError: Identifiable {
+    var id: String? {
+        errorDescription
+    }
+}