| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import CoreData
- import Foundation
- import SwiftDate
- import Swinject
- protocol TempTargetsObserver {
- func tempTargetsDidUpdate(_ targets: [TempTarget])
- }
- protocol TempTargetsStorage {
- func storeTempTarget(tempTarget: TempTarget) async
- func saveTempTargetsToStorage(_ targets: [TempTarget])
- func fetchForTempTargetPresets() async -> [NSManagedObjectID]
- func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID
- func deleteOverridePreset(_ objectID: NSManagedObjectID) async
- func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
- func syncDate() -> Date
- func recent() -> [TempTarget]
- func nightscoutTreatmentsNotUploaded() -> [NightscoutTreatment]
- func presets() -> [TempTarget]
- func current() -> TempTarget?
- }
- final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
- private let processQueue = DispatchQueue(label: "BaseTempTargetsStorage.processQueue")
- @Injected() private var storage: FileStorage!
- @Injected() private var broadcaster: Broadcaster!
- private let backgroundContext = CoreDataStack.shared.newTaskContext()
- private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
- init(resolver: Resolver) {
- injectServices(resolver)
- }
- func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
- let results = await CoreDataStack.shared.fetchEntitiesAsync(
- ofType: TempTargetStored.self,
- onContext: backgroundContext,
- predicate: NSPredicate.lastActiveTempTarget,
- key: "orderPosition",
- ascending: true,
- fetchLimit: fetchLimit
- )
- guard let fetchedResults = results as? [TempTargetStored] else { return [] }
- return await backgroundContext.perform {
- return fetchedResults.map(\.objectID)
- }
- }
- /// Returns the NSManagedObjectID of the Temp Target Presets
- func fetchForTempTargetPresets() async -> [NSManagedObjectID] {
- let results = await CoreDataStack.shared.fetchEntitiesAsync(
- ofType: TempTargetStored.self,
- onContext: backgroundContext,
- predicate: NSPredicate.allTempTargetPresets,
- key: "orderPosition",
- ascending: true
- )
- guard let fetchedResults = results as? [TempTargetStored] else { return [] }
- return await backgroundContext.perform {
- return fetchedResults.map(\.objectID)
- }
- }
- func storeTempTarget(tempTarget: TempTarget) async {
- var presetCount = -1
- if tempTarget.isPreset == true {
- let presets = await fetchForTempTargetPresets()
- presetCount = presets.count
- }
- await backgroundContext.perform {
- let newTempTarget = TempTargetStored(context: self.backgroundContext)
- newTempTarget.date = tempTarget.createdAt
- newTempTarget.id = UUID()
- newTempTarget.enabled = tempTarget.enabled ?? false
- newTempTarget.duration = tempTarget.duration as NSDecimalNumber
- newTempTarget.isUploadedToNS = false
- newTempTarget.name = tempTarget.name
- newTempTarget.target = NSDecimalNumber(decimal: tempTarget.targetTop ?? 0)
- newTempTarget.isPreset = tempTarget.isPreset ?? false
- newTempTarget.halfBasalTarget = NSDecimalNumber(decimal: tempTarget.halfBasalTarget ?? 160)
- // Set order position if we have a valid count and the temp target is a preset
- if tempTarget.isPreset == true, presetCount > -1 {
- newTempTarget.orderPosition = Int16(presetCount + 1)
- }
- do {
- guard self.backgroundContext.hasChanges else { return }
- try self.backgroundContext.save()
- } catch let error as NSError {
- debugPrint(
- "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Temp Target to Core Data with error: \(error.userInfo)"
- )
- }
- }
- }
- func saveTempTargetsToStorage(_ targets: [TempTarget]) {
- processQueue.async {
- let file = OpenAPS.Settings.tempTargets
- var uniqEvents: [TempTarget] = []
- self.storage.transaction { storage in
- storage.append(targets, to: file, uniqBy: \.createdAt)
- let retrievedTargets = storage.retrieve(file, as: [TempTarget].self) ?? []
- uniqEvents = retrievedTargets
- .filter { $0.isWithinLastDay }
- .sorted(by: { $0.createdAt > $1.createdAt })
- storage.save(uniqEvents, as: file)
- }
- self.broadcaster.notify(TempTargetsObserver.self, on: self.processQueue) {
- $0.tempTargetsDidUpdate(uniqEvents)
- }
- }
- }
- // Copy the current Temp Target if it is a RUNNING Preset
- /// otherwise we would edit the Preset
- @MainActor func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID {
- let newTempTarget = TempTargetStored(context: viewContext)
- newTempTarget.date = tempTarget.date
- newTempTarget.id = tempTarget.id
- newTempTarget.enabled = tempTarget.enabled
- newTempTarget.duration = tempTarget.duration
- newTempTarget.isUploadedToNS = true // to avoid getting duplicates on NS
- newTempTarget.name = tempTarget.name
- newTempTarget.target = tempTarget.target
- newTempTarget.isPreset = false // no Preset
- newTempTarget.halfBasalTarget = tempTarget.halfBasalTarget
- await viewContext.perform {
- do {
- guard self.viewContext.hasChanges else { return }
- try self.viewContext.save()
- } catch let error as NSError {
- debugPrint(
- "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to copy Temp Target with error: \(error.userInfo)"
- )
- }
- }
- return newTempTarget.objectID
- }
- @MainActor func deleteOverridePreset(_ objectID: NSManagedObjectID) async {
- await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
- }
- func syncDate() -> Date {
- Date().addingTimeInterval(-1.days.timeInterval)
- }
- func recent() -> [TempTarget] {
- storage.retrieve(OpenAPS.Settings.tempTargets, as: [TempTarget].self)?.reversed() ?? []
- }
- func current() -> TempTarget? {
- guard let last = recent().last else {
- return nil
- }
- guard last.createdAt.addingTimeInterval(Int(last.duration).minutes.timeInterval) > Date(), last.createdAt <= Date(),
- last.duration != 0
- else {
- return nil
- }
- return last
- }
- func nightscoutTreatmentsNotUploaded() -> [NightscoutTreatment] {
- let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedTempTargets, as: [NightscoutTreatment].self) ?? []
- let eventsManual = recent().filter { $0.enteredBy == TempTarget.manual }
- let treatments = eventsManual.map {
- NightscoutTreatment(
- duration: Int($0.duration),
- rawDuration: nil,
- rawRate: nil,
- absolute: nil,
- rate: nil,
- eventType: .nsTempTarget,
- createdAt: $0.createdAt,
- enteredBy: TempTarget.manual,
- bolus: nil,
- insulin: nil,
- notes: nil,
- carbs: nil,
- targetTop: $0.targetTop,
- targetBottom: $0.targetBottom
- )
- }
- return Array(Set(treatments).subtracting(Set(uploaded)))
- }
- func presets() -> [TempTarget] {
- storage.retrieve(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self)?.reversed() ?? []
- }
- }
- private extension TempTarget {
- var isActive: Bool {
- let expirationTime = createdAt.addingTimeInterval(Int(duration).minutes.timeInterval)
- return expirationTime > Date() && createdAt <= Date()
- }
- var isWithinLastDay: Bool {
- createdAt.addingTimeInterval(1.days.timeInterval) > Date()
- }
- }
|