Skip to content

Commit d011b12

Browse files
author
Rick Pasetto
authored
LOOP-1114: Adds VersionCheckServicesManager, a "sister" to other Service managers (#453)
* ckpt * Show version update in loop icon if allowDebugFeatures * adds unit tests * fix tyop * PR Feedback * More PR feedback
1 parent 4b97e38 commit d011b12

File tree

6 files changed

+158
-7
lines changed

6 files changed

+158
-7
lines changed

Loop.xcodeproj/project.pbxproj

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
1D4990E824A25931005CC357 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89E267FB2292456700A3F2AF /* FeatureFlags.swift */; };
3434
1D4A3E2D2478628500FD601B /* StoredAlert+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4A3E2B2478628500FD601B /* StoredAlert+CoreDataClass.swift */; };
3535
1D4A3E2E2478628500FD601B /* StoredAlert+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4A3E2C2478628500FD601B /* StoredAlert+CoreDataProperties.swift */; };
36+
1D63DEA526E950D400F46FA5 /* VersionCheckServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D63DEA426E950D400F46FA5 /* VersionCheckServicesManager.swift */; };
3637
1D6B1B6726866D89009AC446 /* AlertPermissionsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6B1B6626866D89009AC446 /* AlertPermissionsChecker.swift */; };
38+
1D70C40126EC0F9D00C62570 /* VersionCheckServicesManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D70C40026EC0F9D00C62570 /* VersionCheckServicesManagerTests.swift */; };
3739
1D80313D24746274002810DF /* AlertStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D80313C24746274002810DF /* AlertStoreTests.swift */; };
3840
1D82E6A025377C6B009131FB /* TrustedTimeChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D82E69F25377C6B009131FB /* TrustedTimeChecker.swift */; };
3941
1D8D55BC252274650044DBB6 /* BolusEntryViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8D55BB252274650044DBB6 /* BolusEntryViewModelTests.swift */; };
@@ -778,7 +780,9 @@
778780
1D49795724E7289700948F05 /* ServicesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesViewModel.swift; sourceTree = "<group>"; };
779781
1D4A3E2B2478628500FD601B /* StoredAlert+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoredAlert+CoreDataClass.swift"; sourceTree = "<group>"; };
780782
1D4A3E2C2478628500FD601B /* StoredAlert+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoredAlert+CoreDataProperties.swift"; sourceTree = "<group>"; };
783+
1D63DEA426E950D400F46FA5 /* VersionCheckServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionCheckServicesManager.swift; sourceTree = "<group>"; };
781784
1D6B1B6626866D89009AC446 /* AlertPermissionsChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPermissionsChecker.swift; sourceTree = "<group>"; };
785+
1D70C40026EC0F9D00C62570 /* VersionCheckServicesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionCheckServicesManagerTests.swift; sourceTree = "<group>"; };
782786
1D80313C24746274002810DF /* AlertStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertStoreTests.swift; sourceTree = "<group>"; };
783787
1D82E69F25377C6B009131FB /* TrustedTimeChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedTimeChecker.swift; sourceTree = "<group>"; };
784788
1D8D55BB252274650044DBB6 /* BolusEntryViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusEntryViewModelTests.swift; sourceTree = "<group>"; };
@@ -1632,11 +1636,12 @@
16321636
isa = PBXGroup;
16331637
children = (
16341638
1DA7A84024476E98008257F0 /* Alerts */,
1639+
C16575722538AFF6004AE16E /* CGMStalenessMonitorTests.swift */,
16351640
A91E4C2224F86F1000BE9213 /* CriticalEventLogExportManagerTests.swift */,
1641+
C16B983F26B4898800256B05 /* DoseEnactorTests.swift */,
16361642
E9C58A7124DB489100487A17 /* LoopDataManagerTests.swift */,
1643+
1D70C40026EC0F9D00C62570 /* VersionCheckServicesManagerTests.swift */,
16371644
A9F5F1F4251050EC00E7C8A4 /* ZipArchiveTests.swift */,
1638-
C16575722538AFF6004AE16E /* CGMStalenessMonitorTests.swift */,
1639-
C16B983F26B4898800256B05 /* DoseEnactorTests.swift */,
16401645
);
16411646
path = Managers;
16421647
sourceTree = "<group>";
@@ -2100,6 +2105,7 @@
21002105
4F70C20F1DE8FAC5006380B7 /* ExtensionDataManager.swift */,
21012106
89ADE13A226BFA0F0067222B /* TestingScenariosManager.swift */,
21022107
1D82E69F25377C6B009131FB /* TrustedTimeChecker.swift */,
2108+
1D63DEA426E950D400F46FA5 /* VersionCheckServicesManager.swift */,
21032109
4328E0341CFC0AE100E199AA /* WatchDataManager.swift */,
21042110
1DA6499D2441266400F61E75 /* Alerts */,
21052111
E95D37FF24EADE68005E2F50 /* Store Protocols */,
@@ -3532,6 +3538,7 @@
35323538
899433B823FE129800FA4BEA /* OverrideBadgeView.swift in Sources */,
35333539
89D1503E24B506EB00EDE253 /* Dictionary.swift in Sources */,
35343540
4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */,
3541+
1D63DEA526E950D400F46FA5 /* VersionCheckServicesManager.swift in Sources */,
35353542
4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */,
35363543
43E93FB61E469A4000EAB8DB /* NumberFormatter.swift in Sources */,
35373544
C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */,
@@ -3798,6 +3805,7 @@
37983805
C16575732538AFF6004AE16E /* CGMStalenessMonitorTests.swift in Sources */,
37993806
A9E6DFEA246A0448005B1A1C /* PumpManagerErrorTests.swift in Sources */,
38003807
1DA7A84424477698008257F0 /* InAppModalAlertIssuerTests.swift in Sources */,
3808+
1D70C40126EC0F9D00C62570 /* VersionCheckServicesManagerTests.swift in Sources */,
38013809
E93E86A824DDCC4400FF40C8 /* MockDoseStore.swift in Sources */,
38023810
E98A55F124EDD85E0008715D /* MockDosingDecisionStore.swift in Sources */,
38033811
8968B114240C55F10074BB48 /* LoopSettingsTests.swift in Sources */,

Loop/Managers/DeviceDataManager.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,15 @@ final class DeviceDataManager {
312312
glucoseStore: glucoseStore,
313313
settingsStore: settingsStore
314314
)
315+
316+
let versionCheckServicesManager = VersionCheckServicesManager()
315317

316318
servicesManager = ServicesManager(
317319
pluginManager: pluginManager,
318320
analyticsServicesManager: analyticsServicesManager,
319321
loggingServicesManager: loggingServicesManager,
320-
remoteDataServicesManager: remoteDataServicesManager
322+
remoteDataServicesManager: remoteDataServicesManager,
323+
versionCheckServicesManager: versionCheckServicesManager
321324
)
322325

323326
let criticalEventLogs: [CriticalEventLog] = [settingsStore, glucoseStore, carbStore, dosingDecisionStore, doseStore, deviceLog, alertManager.alertStore]

Loop/Managers/ServicesManager.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class ServicesManager {
1919
let loggingServicesManager: LoggingServicesManager
2020

2121
let remoteDataServicesManager: RemoteDataServicesManager
22+
23+
let versionCheckServicesManager: VersionCheckServicesManager
2224

2325
private var services = [Service]()
2426

@@ -30,13 +32,14 @@ class ServicesManager {
3032
pluginManager: PluginManager,
3133
analyticsServicesManager: AnalyticsServicesManager,
3234
loggingServicesManager: LoggingServicesManager,
33-
remoteDataServicesManager: RemoteDataServicesManager
35+
remoteDataServicesManager: RemoteDataServicesManager,
36+
versionCheckServicesManager: VersionCheckServicesManager
3437
) {
3538
self.pluginManager = pluginManager
3639
self.analyticsServicesManager = analyticsServicesManager
3740
self.loggingServicesManager = loggingServicesManager
3841
self.remoteDataServicesManager = remoteDataServicesManager
39-
42+
self.versionCheckServicesManager = versionCheckServicesManager
4043
restoreState()
4144
}
4245

@@ -115,6 +118,9 @@ class ServicesManager {
115118
if let remoteDataService = service as? RemoteDataService {
116119
remoteDataServicesManager.addService(remoteDataService)
117120
}
121+
if let versionCheckService = service as? VersionCheckService {
122+
versionCheckServicesManager.addService(versionCheckService)
123+
}
118124

119125
saveState()
120126
}
@@ -131,6 +137,9 @@ class ServicesManager {
131137
if let analyticsService = service as? AnalyticsService {
132138
analyticsServicesManager.removeService(analyticsService)
133139
}
140+
if let versionCheckService = service as? VersionCheckService {
141+
versionCheckServicesManager.removeService(versionCheckService)
142+
}
134143

135144
services.removeAll { $0.serviceIdentifier == service.serviceIdentifier }
136145

@@ -160,6 +169,9 @@ class ServicesManager {
160169
if let remoteDataService = service as? RemoteDataService {
161170
remoteDataServicesManager.restoreService(remoteDataService)
162171
}
172+
if let versionCheckService = service as? VersionCheckService {
173+
versionCheckServicesManager.restoreService(versionCheckService)
174+
}
163175
}
164176
}
165177
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// VersionCheckServicesManager.swift
3+
// Loop
4+
//
5+
// Created by Rick Pasetto on 9/8/21.
6+
// Copyright © 2021 LoopKit Authors. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import LoopKit
11+
12+
final class VersionCheckServicesManager {
13+
14+
private lazy var log = DiagnosticLog(category: "VersionCheckServicesManager")
15+
16+
private lazy var dispatchQueue = DispatchQueue(label: "com.loopkit.Loop.VersionCheckServicesManager")
17+
18+
private var versionCheckServices = Locked<[VersionCheckService]>([])
19+
20+
init() {}
21+
22+
func addService(_ versionCheckService: VersionCheckService) {
23+
versionCheckServices.mutate { $0.append(versionCheckService) }
24+
}
25+
26+
func restoreService(_ versionCheckService: VersionCheckService) {
27+
versionCheckServices.mutate { $0.append(versionCheckService) }
28+
}
29+
30+
func removeService(_ versionCheckService: VersionCheckService) {
31+
versionCheckServices.mutate { $0.removeAll { $0.serviceIdentifier == versionCheckService.serviceIdentifier } }
32+
}
33+
34+
func checkVersion(currentVersion: String) -> VersionUpdate {
35+
let semaphore = DispatchSemaphore(value: 0)
36+
var results = [String: Result<VersionUpdate?, Error>]()
37+
let services = versionCheckServices.value
38+
services.forEach { versionCheckService in
39+
dispatchQueue.async {
40+
versionCheckService.checkVersion(bundleIdentifier: Bundle.main.bundleIdentifier!, currentVersion: currentVersion) { result in
41+
self.dispatchQueue.async {
42+
results[versionCheckService.serviceIdentifier] = result
43+
semaphore.signal()
44+
}
45+
}
46+
}
47+
semaphore.wait()
48+
}
49+
50+
var aggregatedVersionUpdate = VersionUpdate.noneNeeded
51+
results.forEach { key, value in
52+
switch value {
53+
case .failure(let error):
54+
self.log.error("Error from version check service %{public}@: %{public}@", key, error.localizedDescription)
55+
case .success(let versionUpdate):
56+
if let versionUpdate = versionUpdate, versionUpdate > aggregatedVersionUpdate {
57+
aggregatedVersionUpdate = versionUpdate
58+
}
59+
}
60+
}
61+
return aggregatedVersionUpdate
62+
}
63+
64+
}

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,10 +1516,14 @@ final class StatusTableViewController: LoopChartsTableViewController {
15161516

15171517
@objc private func showLoopCompletionMessage(_: Any) {
15181518
guard let loopCompletionMessage = hudView?.loopCompletionHUD.loopCompletionMessage else { return }
1519-
presentLoopCompletionMesage(title: loopCompletionMessage.title, message: loopCompletionMessage.message)
1519+
var message = loopCompletionMessage.message
1520+
if FeatureFlags.allowDebugFeatures {
1521+
message.append("\n\nVersion \(Bundle.main.shortVersionString): \(deviceManager.servicesManager.versionCheckServicesManager.checkVersion(currentVersion: Bundle.main.shortVersionString).localizedDescription)")
1522+
}
1523+
presentLoopCompletionMessage(title: loopCompletionMessage.title, message: message)
15201524
}
15211525

1522-
private func presentLoopCompletionMesage(title: String, message: String) {
1526+
private func presentLoopCompletionMessage(title: String, message: String) {
15231527
let action = UIAlertAction(title: NSLocalizedString("Dismiss", comment: "The button label of the action used to dismiss an error alert"),
15241528
style: .default)
15251529
let alertController = UIAlertController(title: title,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// VersionCheckServicesManagerTests.swift
3+
// LoopTests
4+
//
5+
// Created by Rick Pasetto on 9/10/21.
6+
// Copyright © 2021 LoopKit Authors. All rights reserved.
7+
//
8+
9+
import XCTest
10+
import LoopKit
11+
@testable import Loop
12+
13+
class VersionCheckServicesManagerTests: XCTestCase {
14+
15+
class MockVersionCheckService: VersionCheckService {
16+
var mockResult: Result<VersionUpdate?, Error> = .success(.noneNeeded)
17+
func checkVersion(bundleIdentifier: String, currentVersion: String, completion: @escaping (Result<VersionUpdate?, Error>) -> Void) {
18+
completion(mockResult)
19+
}
20+
convenience init() { self.init(rawState: [:])! }
21+
static var localizedTitle = "MockVersionCheckService"
22+
static var serviceIdentifier = "MockVersionCheckService"
23+
var serviceDelegate: ServiceDelegate?
24+
required init?(rawState: RawStateValue) { }
25+
var rawState: RawStateValue = [:]
26+
var isOnboarded: Bool = false
27+
}
28+
29+
var versionCheckServicesManager: VersionCheckServicesManager!
30+
var mockVersionCheckService: MockVersionCheckService!
31+
32+
override func setUp() {
33+
versionCheckServicesManager = VersionCheckServicesManager()
34+
mockVersionCheckService = MockVersionCheckService()
35+
versionCheckServicesManager.addService(mockVersionCheckService)
36+
}
37+
38+
func testVersionCheckOneService() throws {
39+
XCTAssertEqual(.noneNeeded, versionCheckServicesManager.checkVersion(currentVersion: ""))
40+
mockVersionCheckService.mockResult = .success(.criticalNeeded)
41+
XCTAssertEqual(.criticalNeeded, versionCheckServicesManager.checkVersion(currentVersion: ""))
42+
}
43+
44+
enum MockError: Error { case nothing }
45+
func testVersionCheckOneServiceError() throws {
46+
// Error doesn't really do anything but log
47+
mockVersionCheckService.mockResult = .failure(MockError.nothing)
48+
XCTAssertEqual(.noneNeeded, versionCheckServicesManager.checkVersion(currentVersion: ""))
49+
}
50+
51+
func testVersionCheckMultipleServices() throws {
52+
let anotherService = MockVersionCheckService()
53+
versionCheckServicesManager.addService(anotherService)
54+
XCTAssertEqual(.noneNeeded, versionCheckServicesManager.checkVersion(currentVersion: ""))
55+
anotherService.mockResult = .success(.criticalNeeded)
56+
XCTAssertEqual(.criticalNeeded, versionCheckServicesManager.checkVersion(currentVersion: ""))
57+
mockVersionCheckService.mockResult = .success(.supportedNeeded)
58+
XCTAssertEqual(.criticalNeeded, versionCheckServicesManager.checkVersion(currentVersion: ""))
59+
}
60+
}

0 commit comments

Comments
 (0)