FreeAPSApp.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import ActivityKit
  2. import BackgroundTasks
  3. import CoreData
  4. import Foundation
  5. import SwiftUI
  6. import Swinject
  7. @main struct FreeAPSApp: App {
  8. @Environment(\.scenePhase) var scenePhase
  9. @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
  10. // Read the color scheme preference from UserDefaults; defaults to system default setting
  11. @AppStorage("colorSchemePreference") private var colorSchemePreference: ColorSchemeOption = .systemDefault
  12. let coreDataStack = CoreDataStack.shared
  13. // Dependencies Assembler
  14. // contain all dependencies Assemblies
  15. // TODO: Remove static key after update "Use Dependencies" logic
  16. private static var assembler = Assembler([
  17. StorageAssembly(),
  18. ServiceAssembly(),
  19. APSAssembly(),
  20. NetworkAssembly(),
  21. UIAssembly(),
  22. SecurityAssembly()
  23. ], parent: nil, defaultObjectScope: .container)
  24. var resolver: Resolver {
  25. FreeAPSApp.assembler.resolver
  26. }
  27. // Temp static var
  28. // Use to backward compatibility with old Dependencies logic on Logger
  29. // TODO: Remove var after update "Use Dependencies" logic in Logger
  30. static var resolver: Resolver {
  31. FreeAPSApp.assembler.resolver
  32. }
  33. private func loadServices() {
  34. resolver.resolve(AppearanceManager.self)!.setupGlobalAppearance()
  35. _ = resolver.resolve(DeviceDataManager.self)!
  36. _ = resolver.resolve(APSManager.self)!
  37. _ = resolver.resolve(FetchGlucoseManager.self)!
  38. _ = resolver.resolve(FetchTreatmentsManager.self)!
  39. _ = resolver.resolve(FetchAnnouncementsManager.self)!
  40. _ = resolver.resolve(CalendarManager.self)!
  41. _ = resolver.resolve(UserNotificationsManager.self)!
  42. _ = resolver.resolve(WatchManager.self)!
  43. _ = resolver.resolve(HealthKitManager.self)!
  44. _ = resolver.resolve(BluetoothStateManager.self)!
  45. _ = resolver.resolve(PluginManager.self)!
  46. if #available(iOS 16.2, *) {
  47. _ = resolver.resolve(LiveActivityBridge.self)!
  48. }
  49. }
  50. init() {
  51. debug(
  52. .default,
  53. "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(BuildDetails.default.buildDate())] [buildExpires: \(BuildDetails.default.calculateExpirationDate())]"
  54. )
  55. loadServices()
  56. // Clear the persistentHistory and the NSManagedObjects that are older than 90 days every time the app starts
  57. cleanupOldData()
  58. }
  59. var body: some Scene {
  60. WindowGroup {
  61. Main.RootView(resolver: resolver)
  62. .preferredColorScheme(colorScheme(for: colorSchemePreference ?? .systemDefault) ?? nil)
  63. .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
  64. .environmentObject(Icons())
  65. .onOpenURL(perform: handleURL)
  66. }
  67. .onChange(of: scenePhase) { newScenePhase in
  68. debug(.default, "APPLICATION PHASE: \(newScenePhase)")
  69. /// If the App goes to the background we should ensure that all the changes are saved from the viewContext to the Persistent Container
  70. if newScenePhase == .background {
  71. coreDataStack.save()
  72. }
  73. }
  74. .backgroundTask(.appRefresh("com.openiaps.cleanup")) {
  75. await scheduleDatabaseCleaning()
  76. await cleanupOldData()
  77. }
  78. }
  79. private func colorScheme(for colorScheme: ColorSchemeOption) -> ColorScheme? {
  80. switch colorScheme {
  81. case .systemDefault:
  82. return nil // Uses the system theme.
  83. case .light:
  84. return .light
  85. case .dark:
  86. return .dark
  87. }
  88. }
  89. func scheduleDatabaseCleaning() {
  90. let request = BGAppRefreshTaskRequest(identifier: "com.openiaps.cleanup")
  91. request.earliestBeginDate = .now.addingTimeInterval(7 * 24 * 60 * 60) // 7 days
  92. do {
  93. try BGTaskScheduler.shared.submit(request)
  94. debugPrint("Task scheduled successfully")
  95. } catch {
  96. debugPrint("Failed to schedule tasks")
  97. }
  98. }
  99. private func cleanupOldData() {
  100. Task {
  101. async let cleanupTokens: () = coreDataStack.cleanupPersistentHistoryTokens(before: Date.oneWeekAgo)
  102. async let purgeData: () = purgeOldNSManagedObjects()
  103. await cleanupTokens
  104. try await purgeData
  105. }
  106. }
  107. private func purgeOldNSManagedObjects() async throws {
  108. async let glucoseDeletion: () = coreDataStack.batchDeleteOlderThan(GlucoseStored.self, dateKey: "date", days: 90)
  109. async let pumpEventDeletion: () = coreDataStack.batchDeleteOlderThan(PumpEventStored.self, dateKey: "timestamp", days: 90)
  110. async let bolusDeletion: () = coreDataStack.batchDeleteOlderThan(
  111. parentType: PumpEventStored.self,
  112. childType: BolusStored.self,
  113. dateKey: "timestamp",
  114. days: 90,
  115. relationshipKey: "pumpEvent"
  116. )
  117. async let tempBasalDeletion: () = coreDataStack.batchDeleteOlderThan(
  118. parentType: PumpEventStored.self,
  119. childType: TempBasalStored.self,
  120. dateKey: "timestamp",
  121. days: 90,
  122. relationshipKey: "pumpEvent"
  123. )
  124. async let determinationDeletion: () = coreDataStack
  125. .batchDeleteOlderThan(OrefDetermination.self, dateKey: "deliverAt", days: 90)
  126. async let batteryDeletion: () = coreDataStack.batchDeleteOlderThan(OpenAPS_Battery.self, dateKey: "date", days: 90)
  127. async let carbEntryDeletion: () = coreDataStack.batchDeleteOlderThan(CarbEntryStored.self, dateKey: "date", days: 90)
  128. async let forecastDeletion: () = coreDataStack.batchDeleteOlderThan(Forecast.self, dateKey: "date", days: 2)
  129. async let forecastValueDeletion: () = coreDataStack.batchDeleteOlderThan(
  130. parentType: Forecast.self,
  131. childType: ForecastValue.self,
  132. dateKey: "date",
  133. days: 2,
  134. relationshipKey: "forecast"
  135. )
  136. async let overrideDeletion: () = coreDataStack
  137. .batchDeleteOlderThan(OverrideStored.self, dateKey: "date", days: 3, isPresetKey: "isPreset")
  138. async let overrideRunDeletion: () = coreDataStack
  139. .batchDeleteOlderThan(OverrideRunStored.self, dateKey: "startDate", days: 3)
  140. // Await each task to ensure they are all completed
  141. try await glucoseDeletion
  142. try await pumpEventDeletion
  143. try await bolusDeletion
  144. try await tempBasalDeletion
  145. try await determinationDeletion
  146. try await batteryDeletion
  147. try await carbEntryDeletion
  148. try await forecastDeletion
  149. try await forecastValueDeletion
  150. try await overrideDeletion
  151. try await overrideRunDeletion
  152. }
  153. private func handleURL(_ url: URL) {
  154. let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
  155. switch components?.host {
  156. case "device-select-resp":
  157. resolver.resolve(NotificationCenter.self)!.post(name: .openFromGarminConnect, object: url)
  158. default: break
  159. }
  160. }
  161. }