JSONImporter.swift 5.5 KB

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