Skip to content

Commit

Permalink
LOOP-873 Upload Loop glucose and carbohydrate data to Tidepool backend (
Browse files Browse the repository at this point in the history
#7)

- https://tidepool.atlassian.net/browse/LOOP-873
- Upload carbohydrate and glucose data to Tidepool backend
- Delete carbohydrate data from Tidepool backend
- Add DeletedCarbEntry extension to generate TSelector
- Add StoredCarbEntry extension to generate TDatum
- Add StoredGlucose extension to generate TDatum
- Add default origin for main bundle
  • Loading branch information
Darin Krauss authored Apr 24, 2020
1 parent 88720e3 commit 752e510
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 0 deletions.
24 changes: 24 additions & 0 deletions TidepoolService.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@
A9151367244E2A9E00116932 /* TidepoolKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9BF371E24181977008D7F34 /* TidepoolKitUI.framework */; };
A9151368244E2A9E00116932 /* TidepoolServiceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; };
A92E770122E9181500591027 /* TidepoolServiceSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92E770022E9181500591027 /* TidepoolServiceSetupViewController.swift */; };
A9309CA3243563CD00E02268 /* TOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9309CA2243563CD00E02268 /* TOrigin.swift */; };
A9309CA52435986300E02268 /* DeletedCarbEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9309CA42435986300E02268 /* DeletedCarbEntry.swift */; };
A9309CA72435987000E02268 /* StoredCarbEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9309CA62435987000E02268 /* StoredCarbEntry.swift */; };
A9309CAF2436C52900E02268 /* StoredGlucoseSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9309CAE2436C52900E02268 /* StoredGlucoseSample.swift */; };
A94AE4E8235A89B5005CA320 /* TidepoolServiceKitPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = A94AE4E6235A89B5005CA320 /* TidepoolServiceKitPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; };
A94AE4F2235A8B75005CA320 /* TidepoolServiceKitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A94AE4F1235A8B75005CA320 /* TidepoolServiceKitPlugin.swift */; };
A94AE4F5235A8BAC005CA320 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A94AE4F4235A8BAC005CA320 /* OSLog.swift */; };
A94AE4F8235A909A005CA320 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A94AE4F7235A909A005CA320 /* UIColor.swift */; };
A97651752421AA10002EB5D4 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A94AE4F4235A8BAC005CA320 /* OSLog.swift */; };
A97651762421AA11002EB5D4 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A94AE4F4235A8BAC005CA320 /* OSLog.swift */; };
A97A60FB243818C900AD69A5 /* TDatum.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97A60FA243818C900AD69A5 /* TDatum.swift */; };
A9BF371D2418195C008D7F34 /* TidepoolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9BF371C2418195C008D7F34 /* TidepoolKit.framework */; };
A9BF371F24181978008D7F34 /* TidepoolKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9BF371E24181977008D7F34 /* TidepoolKitUI.framework */; };
A9D1107C242289720091C620 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1107B242289720091C620 /* HKUnit.swift */; };
A9DAAD0822E7987800E76C9F /* TidepoolServiceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; };
A9DAAD0F22E7987800E76C9F /* TidepoolServiceKit.h in Headers */ = {isa = PBXBuildFile; fileRef = A9DAAD0122E7987800E76C9F /* TidepoolServiceKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
A9DAAD2422E7988900E76C9F /* TidepoolServiceKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; };
Expand Down Expand Up @@ -106,16 +112,22 @@
/* Begin PBXFileReference section */
A913B37C24200C97000805C4 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
A92E770022E9181500591027 /* TidepoolServiceSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TidepoolServiceSetupViewController.swift; sourceTree = "<group>"; };
A9309CA2243563CD00E02268 /* TOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOrigin.swift; sourceTree = "<group>"; };
A9309CA42435986300E02268 /* DeletedCarbEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedCarbEntry.swift; sourceTree = "<group>"; };
A9309CA62435987000E02268 /* StoredCarbEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredCarbEntry.swift; sourceTree = "<group>"; };
A9309CAE2436C52900E02268 /* StoredGlucoseSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredGlucoseSample.swift; sourceTree = "<group>"; };
A94AE4E4235A89B5005CA320 /* TidepoolServiceKitPlugin.loopplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TidepoolServiceKitPlugin.loopplugin; sourceTree = BUILT_PRODUCTS_DIR; };
A94AE4E6235A89B5005CA320 /* TidepoolServiceKitPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TidepoolServiceKitPlugin.h; sourceTree = "<group>"; };
A94AE4E7235A89B5005CA320 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A94AE4F1235A8B75005CA320 /* TidepoolServiceKitPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TidepoolServiceKitPlugin.swift; sourceTree = "<group>"; };
A94AE4F4235A8BAC005CA320 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
A94AE4F7235A909A005CA320 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
A97A60FA243818C900AD69A5 /* TDatum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDatum.swift; sourceTree = "<group>"; };
A99222FA235A879600C11C04 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
A99222FB235A87B100C11C04 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
A9BF371C2418195C008D7F34 /* TidepoolKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A9BF371E24181977008D7F34 /* TidepoolKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A9D1107B242289720091C620 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = "<group>"; };
A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TidepoolServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A9DAAD0122E7987800E76C9F /* TidepoolServiceKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TidepoolServiceKit.h; sourceTree = "<group>"; };
A9DAAD0222E7987800E76C9F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -217,6 +229,12 @@
isa = PBXGroup;
children = (
A913B37C24200C97000805C4 /* Bundle.swift */,
A9309CA42435986300E02268 /* DeletedCarbEntry.swift */,
A9D1107B242289720091C620 /* HKUnit.swift */,
A9309CA62435987000E02268 /* StoredCarbEntry.swift */,
A9309CAE2436C52900E02268 /* StoredGlucoseSample.swift */,
A97A60FA243818C900AD69A5 /* TDatum.swift */,
A9309CA2243563CD00E02268 /* TOrigin.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -672,8 +690,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A9309CA52435986300E02268 /* DeletedCarbEntry.swift in Sources */,
A9309CAF2436C52900E02268 /* StoredGlucoseSample.swift in Sources */,
A9D1107C242289720091C620 /* HKUnit.swift in Sources */,
A9309CA72435987000E02268 /* StoredCarbEntry.swift in Sources */,
A97A60FB243818C900AD69A5 /* TDatum.swift in Sources */,
A9DAAD3322E7CA1A00E76C9F /* LocalizedString.swift in Sources */,
A913B37D24200C97000805C4 /* Bundle.swift in Sources */,
A9309CA3243563CD00E02268 /* TOrigin.swift in Sources */,
A97651752421AA10002EB5D4 /* OSLog.swift in Sources */,
A9DAAD3622E7CAC100E76C9F /* TidepoolService.swift in Sources */,
);
Expand Down
20 changes: 20 additions & 0 deletions TidepoolServiceKit/Extensions/DeletedCarbEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// DeletedCarbEntry.swift
// TidepoolServiceKit
//
// Created by Darin Krauss on 4/1/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//

import LoopKit
import TidepoolKit

extension DeletedCarbEntry {
var selector: TDatum.Selector? {
guard let syncIdentifier = syncIdentifier else {
return nil
}
return TDatum.Selector(origin: TDatum.Selector.Origin(id: syncIdentifier))
}
}

19 changes: 19 additions & 0 deletions TidepoolServiceKit/Extensions/HKUnit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// HKUnit.swift
// TidepoolServiceKit
//
// Created by Darin Krauss on 3/18/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//

import HealthKit

extension HKUnit {
public static let milligramsPerDeciliter: HKUnit = {
return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci))
}()

public static let millimolesPerLiter: HKUnit = {
return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter())
}()
}
48 changes: 48 additions & 0 deletions TidepoolServiceKit/Extensions/StoredCarbEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// StoredCarbEntry.swift
// TidepoolServiceKit
//
// Created by Darin Krauss on 4/1/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//

import LoopKit
import TidepoolKit

extension StoredCarbEntry {
var datum: TDatum? {
guard syncIdentifier != nil else {
return nil
}
return TFoodDatum(time: datumTime, name: datumName, nutrition: datumNutrition).adorn(withOrigin: datumOrigin)
}

private var datumTime: Date { startDate }

private var datumName: String? { foodType }

private var datumNutrition: TFoodDatum.Nutrition {
return TFoodDatum.Nutrition(carbohydrate: datumCarbohydrate, estimatedAbsorptionDuration: datumEstimatedAbsorptionDuration)
}

private var datumCarbohydrate: TFoodDatum.Nutrition.Carbohydrate {
return TFoodDatum.Nutrition.Carbohydrate(net: quantity.doubleValue(for: .gram()), units: .grams)
}

private var datumEstimatedAbsorptionDuration: Int? {
guard let absorptionTime = absorptionTime else {
return nil
}
return Int(absorptionTime)
}

private var datumOrigin: TOrigin? {
guard let syncIdentifier = syncIdentifier else {
return nil
}
if !createdByCurrentApp {
return TOrigin(id: syncIdentifier, name: "com.apple.HealthKit", type: .service)
}
return TOrigin(id: syncIdentifier)
}
}
33 changes: 33 additions & 0 deletions TidepoolServiceKit/Extensions/StoredGlucoseSample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// StoredGlucoseSample.swift
// TidepoolServiceKit
//
// Created by Darin Krauss on 4/2/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//

import LoopKit
import TidepoolKit

extension StoredGlucoseSample {
var datum: TDatum {
if isDisplayOnly {
return TCalibrationDeviceEventDatum(time: datumTime, value: datumValue, units: datumUnits).adorn(withOrigin: datumOrigin)
} else {
return TCBGDatum(time: datumTime, value: datumValue, units: datumUnits).adorn(withOrigin: datumOrigin)
}
}

private var datumTime: Date { startDate }

private var datumValue: Double { quantity.doubleValue(for: .milligramsPerDeciliter) }

private var datumUnits: TBloodGlucose.Units { .milligramsPerDeciliter }

private var datumOrigin: TOrigin {
if !provenanceIdentifier.isEmpty && provenanceIdentifier != Bundle.main.bundleIdentifier {
return TOrigin(id: syncIdentifier, name: provenanceIdentifier, type: .application)
}
return TOrigin(id: syncIdentifier)
}
}
35 changes: 35 additions & 0 deletions TidepoolServiceKit/Extensions/TDatum.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// TDatum.swift
// TidepoolServiceKit
//
// Created by Darin Krauss on 4/3/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//

import TidepoolKit

extension TDatum {
func adorn(withOrigin origin: TOrigin?) -> TDatum {
self.origin = origin
return self
}
}

extension TDatum: CustomDebugStringConvertible {
public var debugDescription: String {
guard let data = try? encoder.encode(self) else {
return "error: failure to encode datum as data"
}
guard let string = String(data: data, encoding: .utf8) else {
return "error: failure to encode data as string"
}
return string
}
}

fileprivate let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
encoder.dateEncodingStrategy = .tidepool
return encoder
}()
15 changes: 15 additions & 0 deletions TidepoolServiceKit/Extensions/TOrigin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// TOrigin.swift
// TidepoolServiceKit
//
// Created by Darin Krauss on 4/1/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//

import TidepoolKit

extension TOrigin {
init(id: String) {
self.init(id: id, name: Bundle.main.bundleIdentifier, version: Bundle.main.semanticVersion, type: .application)
}
}
59 changes: 59 additions & 0 deletions TidepoolServiceKit/TidepoolService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,75 @@ extension TidepoolService: RemoteDataService {

public var carbDataLimit: Int? { return 1000 }

public func uploadCarbData(deleted: [DeletedCarbEntry], stored: [StoredCarbEntry], completion: @escaping (Result<Bool, Error>) -> Void) {
createData(stored.compactMap { $0.datum }) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let storedUploaded):
self.deleteData(withSelectors: deleted.compactMap { $0.selector }) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let deletedUploaded):
completion(.success(storedUploaded || deletedUploaded))
}
}
}
}
}

public var doseDataLimit: Int? { return 1000 }

public var dosingDecisionDataLimit: Int? { return 1000 }

public var glucoseDataLimit: Int? { return 1000 }

public func uploadGlucoseData(_ stored: [StoredGlucoseSample], completion: @escaping (Result<Bool, Error>) -> Void) {
createData(stored.compactMap { $0.datum }, completion: completion)
}

public var pumpEventDataLimit: Int? { return 1000 }

public var settingsDataLimit: Int? { return 1000 }

private func createData(_ data: [TDatum], completion: @escaping (Result<Bool, Error>) -> Void) {
if let error = error {
completion(.failure(error))
return
}
guard let session = session, let dataSetId = dataSetId else {
completion(.failure(TidepoolServiceError.configuration))
return
}

tapi.createData(data, dataSetId: dataSetId, session: session) { error in
if let error = error {
completion(.failure(error))
return
}
completion(.success(!data.isEmpty))
}
}

private func deleteData(withSelectors selectors: [TDatum.Selector], completion: @escaping (Result<Bool, Error>) -> Void) {
if let error = error {
completion(.failure(error))
return
}
guard let session = session, let dataSetId = dataSetId else {
completion(.failure(TidepoolServiceError.configuration))
return
}

tapi.deleteData(withSelectors: selectors, dataSetId: dataSetId, session: session) { error in
if let error = error {
completion(.failure(error))
return
}
completion(.success(!selectors.isEmpty))
}
}
}

extension KeychainManager: SessionStorage {
Expand Down

0 comments on commit 752e510

Please sign in to comment.