@@ -812,6 +812,18 @@ extension DeviceDataManager {
812812 self . loopManager. updateRemoteRecommendation ( )
813813 }
814814 }
815+
816+ func enactBolus( units: Double , activationType: BolusActivationType ) async throws {
817+ return try await withCheckedThrowingContinuation { continuation in
818+ enactBolus ( units: units, activationType: activationType) { error in
819+ if let error = error {
820+ continuation. resume ( throwing: error)
821+ return
822+ }
823+ continuation. resume ( )
824+ }
825+ }
826+ }
815827
816828 var pumpManagerStatus : PumpManagerStatus ? {
817829 return pumpManager? . status
@@ -1341,8 +1353,15 @@ extension Notification.Name {
13411353
13421354// MARK: - Remote Notification Handling
13431355extension DeviceDataManager {
1356+
13441357 func handleRemoteNotification( _ notification: [ String : AnyObject ] ) {
1345-
1358+ Task {
1359+ await handleRemoteNotification ( notification)
1360+ }
1361+ }
1362+
1363+ func handleRemoteNotification( _ notification: [ String : AnyObject ] ) async {
1364+
13461365 defer {
13471366 log. default ( " Remote Notification: Finished handling " )
13481367 }
@@ -1351,9 +1370,6 @@ extension DeviceDataManager {
13511370 log. error ( " Remote Notification: Overrides not enabled. " )
13521371 return
13531372 }
1354-
1355- let defaultServiceIdentifier = " NightscoutService "
1356- let serviceIdentifer = notification [ " serviceIdentifier " ] as? String ?? defaultServiceIdentifier
13571373
13581374 if let expirationStr = notification [ " expiration " ] as? String {
13591375 let formatter = ISO8601DateFormatter ( )
@@ -1362,7 +1378,7 @@ extension DeviceDataManager {
13621378 let nowDate = Date ( )
13631379 guard nowDate < expiration else {
13641380 let expiredInterval = nowDate. timeIntervalSince ( expiration)
1365- NotificationManager . sendRemoteCommandExpiredNotification ( timeExpired: expiredInterval)
1381+ await NotificationManager . sendRemoteCommandExpiredNotification ( timeExpired: expiredInterval)
13661382 log. error ( " Remote Notification: Expired: %{public}@ " , String ( describing: notification) )
13671383 return
13681384 }
@@ -1372,156 +1388,175 @@ extension DeviceDataManager {
13721388 }
13731389 }
13741390
1375- let command : RemoteCommand
1391+ let action : RemoteAction
13761392
13771393 do {
1378- command = try RemoteCommand . createRemoteCommand ( notification: notification, allowedPresets : loopManager . settings . overridePresets , defaultAbsorptionTime : carbStore . defaultAbsorptionTimes . medium ) . get ( )
1394+ action = try RemoteAction . createRemoteAction ( notification: notification) . get ( )
13791395 } catch {
13801396 log. error ( " Remote Notification: Parse Error: %{public}@ " , String ( describing: error) )
13811397 return
13821398 }
1383-
1384- switch command {
1385-
1386- case . temporaryScheduleOverride( let remoteOverride) :
1387- log. default ( " Remote Notification: Enacting temporary override: %{public}@ " , String ( describing: remoteOverride) )
1388- activateRemoteOverride ( remoteOverride)
1389- case . cancelTemporaryOverride:
1390- log. default ( " Remote Notification: Canceling temporary override " )
1391- activateRemoteOverride ( nil )
1392- case . bolusEntry( let bolusAmount) :
1393- log. default ( " Remote Notification: Enacting bolus entry: %{public}@ " , String ( describing: bolusAmount) )
1394-
1395- //Remote bolus requires validation from its remote source
1396-
1397- let validationResult = remoteDataServicesManager. validatePushNotificationSource ( notification, serviceIdentifier: serviceIdentifer)
1398-
1399- switch validationResult {
1400- case . success ( ) :
1401- log. info ( " Remote Notification: Validation successful " )
1402- case . failure ( let error) :
1403- NotificationManager . sendRemoteBolusFailureNotification ( for: error, amount: bolusAmount)
1404- log. error ( " Remote Notification: Could not validate notification: %{public}@ " , String ( describing: notification) )
1405- return
1406- }
1407-
1408- guard let maxBolusAmount = loopManager. settings. maximumBolus else {
1409- NotificationManager . sendRemoteBolusFailureNotification ( for: RemoteCommandError . missingMaxBolus, amount: bolusAmount)
1410- log. error ( " Remote Notification: No max bolus detected. Aborting... " )
1411- return
1412- }
1413-
1414- guard bolusAmount. isLessThanOrEqualTo ( maxBolusAmount) else {
1415- NotificationManager . sendRemoteBolusFailureNotification ( for: RemoteCommandError . exceedsMaxBolus, amount: bolusAmount)
1416- log. error ( " Remote Notification: Bolus exceeds maximum allowed. Aborting... " )
1417- return
1418- }
1419-
1420- self. enactRemoteBolus ( units: bolusAmount) { error in
1421- if let error = error {
1422- self . log. error ( " Remote Notification: Error adding bolus: %{public}@ " , String ( describing: error) )
1423- NotificationManager . sendRemoteBolusFailureNotification ( for: error, amount: bolusAmount)
1424- } else {
1425- NotificationManager . sendRemoteBolusNotification ( amount: bolusAmount)
1426- }
1427- }
1428- case . carbsEntry ( let candidateCarbEntry) :
1429- log. default ( " Remote Notification: Adding carbs entry: %{public}@ " , String ( describing: candidateCarbEntry) )
1430-
1431- let candidateCarbsInGrams = candidateCarbEntry. quantity. doubleValue ( for: . gram( ) )
1432-
1433- //Remote carb entry requires validation from its remote source
1434- let validationResult = remoteDataServicesManager. validatePushNotificationSource ( notification, serviceIdentifier: serviceIdentifer)
1435- switch validationResult {
1436- case . success ( ) :
1437- log. info ( " Remote Notification: Validation successful " )
1438- case . failure ( let error) :
1439- NotificationManager . sendRemoteCarbEntryFailureNotification ( for: error, amountInGrams: candidateCarbsInGrams)
1440- log. error ( " Remote Notification: Could not validate notification: %{public}@ " , String ( describing: notification) )
1441- return
1399+
1400+ log. default ( " Remote Notification: Handling action %{public}@ " , String ( describing: action) )
1401+
1402+ switch action {
1403+ case . temporaryScheduleOverride( let overrideAction) :
1404+ do {
1405+ try await handleRemoteOverrideAction ( overrideAction)
1406+ } catch {
1407+ log. error ( " Remote Notification: Override Action Error: %{public}@ " , String ( describing: error) )
14421408 }
1443-
1444- guard candidateCarbsInGrams > 0.0 else {
1445- NotificationManager . sendRemoteCarbEntryFailureNotification ( for : RemoteCommandError . invalidCarbs , amountInGrams : candidateCarbsInGrams )
1446- log . error ( " Remote Notification: Invalid carb entry amount. Aborting... " )
1447- return
1409+ case . cancelTemporaryOverride ( let overrideCancelAction ) :
1410+ do {
1411+ try await handleRemoteOverrideCancelAction ( overrideCancelAction )
1412+ } catch {
1413+ log . error ( " Remote Notification: Override Action Cancel Error: %{public}@ " , String ( describing : error ) )
14481414 }
1449-
1450- guard candidateCarbsInGrams <= LoopConstants . maxCarbEntryQuantity. doubleValue ( for: . gram( ) ) else {
1451- NotificationManager . sendRemoteCarbEntryFailureNotification ( for: RemoteCommandError . exceedsMaxCarbs, amountInGrams: candidateCarbsInGrams)
1452- log. error ( " Remote Notification: Carbs higher than maximum. Aborting... " )
1453- return
1415+ case . bolusEntry( let bolusAction) :
1416+ do {
1417+ try validatePushNotificationSource ( notification: notification)
1418+ try await handleRemoteBolusAction ( bolusAction)
1419+ } catch {
1420+ await NotificationManager . sendRemoteBolusFailureNotification ( for: error, amount: bolusAction. amountInUnits)
1421+ log. error ( " Remote Notification: Bolus Action Error: %{public}@ " , String ( describing: notification) )
14541422 }
1455-
1456- addRemoteCarbEntry( candidateCarbEntry) { carbEntryAddResult in
1457- switch carbEntryAddResult {
1458- case . success( let completedCarbEntry) :
1459- NotificationManager . sendRemoteCarbEntryNotification ( amountInGrams: completedCarbEntry. quantity. doubleValue ( for: . gram( ) ) )
1460- case . failure( let error) :
1461- self . log. error ( " Remote Notification: Error adding carb entry: %{public}@ " , String ( describing: error) )
1462- NotificationManager . sendRemoteCarbEntryFailureNotification ( for: error, amountInGrams: candidateCarbsInGrams)
1463- }
1423+ case . carbsEntry( let carbAction) :
1424+ do {
1425+ try validatePushNotificationSource ( notification: notification)
1426+ try await handleRemoteCarbAction ( carbAction)
1427+ } catch {
1428+ await NotificationManager . sendRemoteCarbEntryFailureNotification ( for: error, amountInGrams: carbAction. amountInGrams)
1429+ log. error ( " Remote Notification: Carb Action Error: %{public}@ " , String ( describing: notification) )
14641430 }
14651431 }
14661432 }
14671433
1468- func activateRemoteOverride( _ remoteOverride: TemporaryScheduleOverride? ) {
1434+ func validatePushNotificationSource( notification: [ String : AnyObject ] ) throws {
1435+
1436+ let defaultServiceIdentifier = " NightscoutService "
1437+ let serviceIdentifer = notification [ " serviceIdentifier " ] as? String ?? defaultServiceIdentifier
1438+
1439+ let validationResult = remoteDataServicesManager. validatePushNotificationSource ( notification, serviceIdentifier: serviceIdentifer)
1440+ switch validationResult {
1441+ case . success ( ) :
1442+ log. info ( " Remote Notification: Validation successful " )
1443+ case . failure ( let error) :
1444+ throw error
1445+ }
1446+ }
1447+
1448+ //Remote Overrides
1449+
1450+ func handleRemoteOverrideAction( _ action: RemoteOverrideAction ) async throws {
1451+ let remoteOverride = try action. toValidOverride ( allowedPresets: loopManager. settings. overridePresets)
1452+ await activateRemoteOverride ( remoteOverride)
1453+ }
1454+
1455+ func handleRemoteOverrideCancelAction( _ cancelAction: RemoteOverrideCancelAction ) async throws {
1456+ await activateRemoteOverride ( nil )
1457+ }
1458+
1459+ func activateRemoteOverride( _ remoteOverride: TemporaryScheduleOverride ? ) async {
14691460 loopManager. mutateSettings { settings in settings. scheduleOverride = remoteOverride }
1470- self . triggerBackgroundUpload ( for: . overrides)
1461+ await remoteDataServicesManager . triggerUpload ( for: . overrides)
14711462 }
14721463
1473- func addRemoteCarbEntry( _ carbEntry : NewCarbEntry , completion: @escaping ( _ result: CarbStoreResult< StoredCarbEntry > ) - > Void) {
1474-
1475- var backgroundTask : UIBackgroundTaskIdentifier ?
1476- backgroundTask = UIApplication . shared. beginBackgroundTask ( withName: " Add Remote Carb Entry " ) {
1477- guard let backgroundTask = backgroundTask else { return }
1478- UIApplication . shared. endBackgroundTask ( backgroundTask)
1479- self . log. error ( " Add Remote Carb Entry background task expired " )
1464+ //Remote Bolus
1465+
1466+ func handleRemoteBolusAction( _ bolusCommand: RemoteBolusAction ) async throws {
1467+ let validBolusAmount = try bolusCommand. toValidBolusAmount ( maximumBolus: loopManager. settings. maximumBolus)
1468+ try await self . enactBolus ( units: validBolusAmount, activationType: . manualNoRecommendation)
1469+ await triggerBackgroundUpload ( for: . dose)
1470+ self . analyticsServicesManager. didBolus ( source: " Remote " , units: validBolusAmount)
1471+ }
1472+
1473+ //Remote Carb Entry
1474+
1475+ func handleRemoteCarbAction( _ carbCommand: RemoteCarbAction ) async throws {
1476+ let candidateCarbEntry = try carbCommand. toValidCarbEntry (
1477+ defaultAbsorptionTime: carbStore. defaultAbsorptionTimes. medium,
1478+ minAbsorptionTime: LoopConstants . minCarbAbsorptionTime,
1479+ maxAbsorptionTime: LoopConstants . maxCarbAbsorptionTime,
1480+ maxCarbEntryQuantity: LoopConstants . maxCarbEntryQuantity. doubleValue ( for: . gram( ) ) ,
1481+ maxCarbEntryPastTime: LoopConstants . maxCarbEntryPastTime,
1482+ maxCarbEntryFutureTime: LoopConstants . maxCarbEntryFutureTime
1483+ )
1484+
1485+ let _ = try await addRemoteCarbEntry ( candidateCarbEntry)
1486+ await triggerBackgroundUpload ( for: . carb)
1487+ }
1488+
1489+ //Can't add this concurrency wrapper method to Loopkit due to the minimum iOS version
1490+ func addRemoteCarbEntry( _ carbEntry: NewCarbEntry ) async throws -> StoredCarbEntry {
1491+ return try await withCheckedThrowingContinuation { continuation in
1492+ carbStore. addCarbEntry ( carbEntry) { result in
1493+ switch result {
1494+ case . success( let storedCarbEntry) :
1495+ self . analyticsServicesManager. didAddCarbs ( source: " Remote " , amount: carbEntry. quantity. doubleValue ( for: . gram( ) ) )
1496+ continuation. resume ( returning: storedCarbEntry)
1497+ case . failure( let error) :
1498+ continuation. resume ( throwing: error)
1499+ }
1500+ }
14801501 }
1481-
1482- carbStore. addCarbEntry ( carbEntry) { result in
1483- self . triggerBackgroundUpload ( for: . carb)
1484- if let backgroundTask = backgroundTask {
1485- UIApplication . shared. endBackgroundTask ( backgroundTask)
1502+ }
1503+
1504+ //Background Uploads
1505+
1506+ func triggerBackgroundUpload( for triggeringType: RemoteDataType ) async {
1507+ Task {
1508+ return try await withCheckedThrowingContinuation { continuation in
1509+ triggerBackgroundUpload ( for: triggeringType) {
1510+ continuation. resume ( )
1511+ }
14861512 }
1487- completion ( result)
1488- self . analyticsServicesManager. didAddCarbs ( source: " Remote " , amount: carbEntry. quantity. doubleValue ( for: . gram( ) ) )
14891513 }
14901514 }
14911515
1492- func enactRemoteBolus ( units : Double , completion: @ escaping ( _ error : Error ? ) -> Void = { _ in } ) {
1516+ func triggerBackgroundUpload ( for triggeringType : RemoteDataType , completion: ( ( ) -> Void ) ? = nil ) {
14931517
14941518 var backgroundTask : UIBackgroundTaskIdentifier ?
1495- backgroundTask = UIApplication . shared. beginBackgroundTask ( withName: " Enact Remote Bolus " ) {
1496- guard let backgroundTask = backgroundTask else { return }
1497- UIApplication . shared. endBackgroundTask ( backgroundTask)
1498- self . log. error ( " Enact Remote Bolus background task expired " )
1499- }
1519+ backgroundTask = beginBackgroundTask ( name: " Remote Data Upload " )
15001520
1501- self . enactBolus ( units: units, activationType: . manualNoRecommendation) { error in
1502- self . triggerBackgroundUpload ( for: . dose)
1503- if let backgroundTask = backgroundTask {
1504- UIApplication . shared. endBackgroundTask ( backgroundTask)
1505- }
1506- completion ( error)
1507- self . analyticsServicesManager. didBolus ( source: " Remote " , units: units)
1521+ self . remoteDataServicesManager. triggerUpload ( for: triggeringType) {
1522+ self . endBackgroundTask ( backgroundTask)
1523+ completion ? ( )
15081524 }
15091525 }
15101526
1511- func triggerBackgroundUpload( for triggeringType: RemoteDataType) {
1512-
1527+ func beginBackgroundTask( name: String ) -> UIBackgroundTaskIdentifier ? {
15131528 var backgroundTask : UIBackgroundTaskIdentifier ?
1514- backgroundTask = UIApplication . shared. beginBackgroundTask ( withName: " Remote Data Upload " ) {
1529+ backgroundTask = UIApplication . shared. beginBackgroundTask ( withName: name ) {
15151530 guard let backgroundTask = backgroundTask else { return }
15161531 UIApplication . shared. endBackgroundTask ( backgroundTask)
1517- self . log. error ( " Remote Data Upload background task expired " )
1532+ self . log. error ( " Background Task Expired: %{public}@ " , name )
15181533 }
15191534
1520- self . remoteDataServicesManager. triggerUpload ( for: triggeringType) {
1521- if let backgroundTask = backgroundTask {
1522- UIApplication . shared. endBackgroundTask ( backgroundTask)
1535+ return backgroundTask
1536+ }
1537+
1538+ func beginBackgroundTask( name: String ) async -> UIBackgroundTaskIdentifier ? {
1539+ var backgroundTask : UIBackgroundTaskIdentifier ?
1540+ backgroundTask = await UIApplication . shared. beginBackgroundTask ( withName: name) {
1541+ guard let backgroundTask = backgroundTask else { return }
1542+ Task {
1543+ await UIApplication . shared. endBackgroundTask ( backgroundTask)
15231544 }
1545+
1546+ self . log. error ( " Background Task Expired: %{public}@ " , name)
15241547 }
1548+
1549+ return backgroundTask
1550+ }
1551+
1552+ func endBackgroundTask( _ backgroundTask: UIBackgroundTaskIdentifier ? ) async {
1553+ guard let backgroundTask else { return }
1554+ await UIApplication . shared. endBackgroundTask ( backgroundTask)
1555+ }
1556+
1557+ func endBackgroundTask( _ backgroundTask: UIBackgroundTaskIdentifier ? ) {
1558+ guard let backgroundTask else { return }
1559+ UIApplication . shared. endBackgroundTask ( backgroundTask)
15251560 }
15261561}
15271562
0 commit comments