MigrationScript.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import CoreData
  2. import Foundation
  3. // MARK: - Protocol Definition
  4. /// A protocol that ensures a Data Transfer Object (DTO) can be stored in Core Data.
  5. /// It requires a method to map the DTO to its corresponding Core Data managed object.
  6. protocol ImportableDTO: Decodable {
  7. associatedtype ManagedObject: NSManagedObject
  8. /// Converts the DTO into a Core Data managed object.
  9. func store(in context: NSManagedObjectContext) -> ManagedObject
  10. }
  11. // MARK: - JSONImporter Class with Generic Import Function
  12. /// Class responsible for importing JSON data into Core Data.
  13. class JSONImporter {
  14. private let context: NSManagedObjectContext
  15. private let fileManager = FileManager.default
  16. /// Initializes the importer with a Core Data context.
  17. init(context: NSManagedObjectContext) {
  18. self.context = context
  19. }
  20. /// Generic function to import data from a JSON file into Core Data.
  21. /// - Parameters:
  22. /// - userDefaultsKey: Key to check if data has already been imported.
  23. /// - filePathComponent: Path component of the JSON file.
  24. /// - dtoType: The DTO type conforming to `ImportableDTO`.
  25. /// - dateDecodingStrategy: The date decoding strategy for JSON decoding.
  26. func importDataIfNeeded<T: ImportableDTO>(
  27. userDefaultsKey: String,
  28. filePathComponent: String,
  29. dtoType _: T.Type,
  30. dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .iso8601
  31. ) async {
  32. let hasImported = UserDefaults.standard.bool(forKey: userDefaultsKey)
  33. guard !hasImported else {
  34. debugPrint("\(filePathComponent) already imported. Skipping import.")
  35. return
  36. }
  37. do {
  38. // Get the file path for the JSON file
  39. guard let filePath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
  40. .first?
  41. .appendingPathComponent(filePathComponent),
  42. fileManager.fileExists(atPath: filePath.path)
  43. else {
  44. debugPrint("\(DebuggingIdentifiers.failed) File not found: \(filePathComponent).")
  45. return
  46. }
  47. let data = try Data(contentsOf: filePath)
  48. let decoder = JSONDecoder()
  49. decoder.dateDecodingStrategy = dateDecodingStrategy
  50. var entries: [T] = []
  51. do {
  52. // Decode as either an array or as a single object
  53. if let array = try? decoder.decode([T].self, from: data) {
  54. debugPrint("\(DebuggingIdentifiers.succeeded) Decoded \(array.count) entries as an array.")
  55. entries = array
  56. } else if let singleObject = try? decoder.decode(T.self, from: data) {
  57. debugPrint("\(DebuggingIdentifiers.succeeded) Decoded a single object.")
  58. entries = [singleObject]
  59. } else {
  60. debugPrint(
  61. "\(DebuggingIdentifiers.failed) Failed to decode \(filePathComponent) as either an array or a single object."
  62. )
  63. return
  64. }
  65. }
  66. // Save the DTOs into Core Data
  67. await context.perform {
  68. for entry in entries {
  69. _ = entry.store(in: self.context)
  70. }
  71. do {
  72. guard self.context.hasChanges else {
  73. return
  74. }
  75. try self.context.save()
  76. debugPrint("\(DebuggingIdentifiers.succeeded) \(filePathComponent) successfully imported into Core Data.")
  77. } catch {
  78. debugPrint("\(DebuggingIdentifiers.failed) Failed to save \(filePathComponent) to Core Data: \(error)")
  79. }
  80. }
  81. // Delete the JSON file after successful import
  82. try fileManager.removeItem(at: filePath)
  83. debugPrint("\(DebuggingIdentifiers.succeeded) \(filePathComponent) deleted after successful import.")
  84. // Update UserDefaults to indicate that the data has been imported
  85. UserDefaults.standard.set(true, forKey: userDefaultsKey)
  86. } catch {
  87. debugPrint("\(DebuggingIdentifiers.failed) Error importing \(filePathComponent): \(error)")
  88. }
  89. }
  90. }
  91. // MARK: - Extension for Specific Import Functions
  92. extension JSONImporter {
  93. func importPumpHistoryIfNeeded() async {
  94. await importDataIfNeeded(
  95. userDefaultsKey: "pumpHistoryImported",
  96. filePathComponent: OpenAPS.Monitor.pumpHistory,
  97. dtoType: PumpEventDTO.self,
  98. dateDecodingStrategy: .iso8601
  99. )
  100. }
  101. func importCarbHistoryIfNeeded() async {
  102. await importDataIfNeeded(
  103. userDefaultsKey: "carbHistoryImported",
  104. filePathComponent: OpenAPS.Monitor.carbHistory,
  105. dtoType: CarbEntryDTO.self,
  106. dateDecodingStrategy: .iso8601
  107. )
  108. }
  109. func importGlucoseHistoryIfNeeded() async {
  110. await importDataIfNeeded(
  111. userDefaultsKey: "glucoseHistoryImported",
  112. filePathComponent: OpenAPS.Monitor.glucose,
  113. dtoType: GlucoseEntryDTO.self,
  114. dateDecodingStrategy: .iso8601
  115. )
  116. }
  117. func importDeterminationHistoryIfNeeded() async {
  118. await importDataIfNeeded(
  119. userDefaultsKey: "enactedHistoryImported",
  120. filePathComponent: OpenAPS.Enact.enacted,
  121. dtoType: DeterminationDTO.self,
  122. dateDecodingStrategy: .iso8601
  123. )
  124. }
  125. }