From e5340f6d56512fecc4e45d866c76ae5c4064e1d4 Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:14:03 -0500 Subject: [PATCH] `AppExpiry` should take database reference as needed This change should have no user impact. --- SignalMessaging/environment/AppSetup.swift | 5 +- .../src/Network/API/HTTPUtils.swift | 2 +- .../src/Network/OWSUrlSession.swift | 2 +- .../src/Network/WebSockets/OWSWebSocket.swift | 4 +- SignalServiceKit/src/SSKEnvironment.swift | 5 ++ .../src/TestUtils/MockSSKEnvironment.swift | 5 +- SignalServiceKit/src/Util/AppExpiry.swift | 82 ++++++++++--------- .../src/Util/RemoteConfigManager.swift | 5 +- 8 files changed, 62 insertions(+), 48 deletions(-) diff --git a/SignalMessaging/environment/AppSetup.swift b/SignalMessaging/environment/AppSetup.swift index dbb15b7dfd..a96d5e1856 100644 --- a/SignalMessaging/environment/AppSetup.swift +++ b/SignalMessaging/environment/AppSetup.swift @@ -112,7 +112,10 @@ public class AppSetup { let modelReadCaches = ModelReadCaches(factory: ModelReadCacheFactory()) let earlyMessageManager = EarlyMessageManager() let messagePipelineSupervisor = MessagePipelineSupervisor.createStandardSupervisor() - let appExpiry = AppExpiry() + let appExpiry = AppExpiry( + keyValueStoreFactory: SDSKeyValueStoreFactory(), + schedulers: DispatchQueueSchedulers() + ) let paymentsHelper = PaymentsHelperImpl() let paymentsCurrencies = PaymentsCurrenciesImpl() let spamChallengeResolver = SpamChallengeResolver() diff --git a/SignalServiceKit/src/Network/API/HTTPUtils.swift b/SignalServiceKit/src/Network/API/HTTPUtils.swift index 77800b4015..06309b4ef0 100644 --- a/SignalServiceKit/src/Network/API/HTTPUtils.swift +++ b/SignalServiceKit/src/Network/API/HTTPUtils.swift @@ -43,7 +43,7 @@ extension HTTPUtils { } if httpError.responseStatusCode == AppExpiry.appExpiredStatusCode { - appExpiry.setHasAppExpiredAtCurrentVersion() + appExpiry.setHasAppExpiredAtCurrentVersion(db: DependenciesBridge.shared.db) } } diff --git a/SignalServiceKit/src/Network/OWSUrlSession.swift b/SignalServiceKit/src/Network/OWSUrlSession.swift index 04a31a61b9..01b6ef5319 100644 --- a/SignalServiceKit/src/Network/OWSUrlSession.swift +++ b/SignalServiceKit/src/Network/OWSUrlSession.swift @@ -472,7 +472,7 @@ public class OWSURLSession: NSObject, OWSURLSessionProtocol { return } - AppExpiry.shared.setHasAppExpiredAtCurrentVersion() + AppExpiry.shared.setHasAppExpiredAtCurrentVersion(db: DependenciesBridge.shared.db) } // MARK: Request building diff --git a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift b/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift index e2dd7edaa1..87e2fbbca6 100644 --- a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift +++ b/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift @@ -469,7 +469,7 @@ public class OWSWebSocket: NSObject { // The websocket is only used to connect to the main signal // service, so we need to check for remote deprecation. if responseStatus == AppExpiry.appExpiredStatusCode { - appExpiry.setHasAppExpiredAtCurrentVersion() + appExpiry.setHasAppExpiredAtCurrentVersion(db: DependenciesBridge.shared.db) } let headers = OWSHttpHeaders() @@ -1186,7 +1186,7 @@ extension OWSWebSocket { }, failure: { (failure: OWSHTTPErrorWrapper) in if failure.error.responseStatusCode == AppExpiry.appExpiredStatusCode { - Self.appExpiry.setHasAppExpiredAtCurrentVersion() + Self.appExpiry.setHasAppExpiredAtCurrentVersion(db: DependenciesBridge.shared.db) } failureParam(failure) diff --git a/SignalServiceKit/src/SSKEnvironment.swift b/SignalServiceKit/src/SSKEnvironment.swift index ecd2de06e3..7c2b93efa0 100644 --- a/SignalServiceKit/src/SSKEnvironment.swift +++ b/SignalServiceKit/src/SSKEnvironment.swift @@ -243,6 +243,11 @@ public class SSKEnvironment: NSObject { warmCachesForObject("paymentsCurrencies", paymentsCurrencies.warmCaches) warmCachesForObject("storyManager", StoryManager.setup) warmCachesForObject("deviceManager", DependenciesBridge.shared.deviceManager.warmCaches) + warmCachesForObject("appExpiry") { + DependenciesBridge.shared.db.read { tx in + self.appExpiry.warmCaches(with: tx) + } + } NotificationCenter.default.post(name: SSKEnvironment.warmCachesNotification, object: nil) } diff --git a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.swift b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.swift index 8d9e4d713f..2e98c0a27a 100644 --- a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.swift +++ b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.swift @@ -109,7 +109,10 @@ public class MockSSKEnvironment: SSKEnvironment { let modelReadCaches = ModelReadCaches(factory: TestableModelReadCacheFactory()) let earlyMessageManager = EarlyMessageManager() let messagePipelineSupervisor = MessagePipelineSupervisor.createStandardSupervisor() - let appExpiry = AppExpiry() + let appExpiry = AppExpiry( + keyValueStoreFactory: SDSKeyValueStoreFactory(), + schedulers: DispatchQueueSchedulers() + ) let paymentsHelper = MockPaymentsHelper() let paymentsCurrencies = MockPaymentsCurrencies() let paymentsEvents = PaymentsEventsNoop() diff --git a/SignalServiceKit/src/Util/AppExpiry.swift b/SignalServiceKit/src/Util/AppExpiry.swift index 263ffbe819..4c9c04d71b 100644 --- a/SignalServiceKit/src/Util/AppExpiry.swift +++ b/SignalServiceKit/src/Util/AppExpiry.swift @@ -10,8 +10,8 @@ public class AppExpiry: NSObject { @objc public static let appExpiredStatusCode: UInt = 499 - @objc - public let keyValueStore = SDSKeyValueStore(collection: "AppExpiry") + private let keyValueStore: KeyValueStore + private let schedulers: Schedulers private struct ExpirationState: Codable, Equatable { let version4: String @@ -37,18 +37,20 @@ public class AppExpiry: NSObject { } } private let expirationState = AtomicValue(.init(mode: .default)) - private static var expirationStateKey: String { "expirationState" } - @objc - public required override init() { + static let keyValueCollection = "AppExpiry" + static let keyValueKey = "expirationState" + + public required init( + keyValueStoreFactory: KeyValueStoreFactory, + schedulers: Schedulers + ) { + self.keyValueStore = keyValueStoreFactory.keyValueStore(collection: Self.keyValueCollection) + self.schedulers = schedulers + super.init() SwiftSingletons.register(self) - - AppReadiness.runNowOrWhenAppWillBecomeReady { - // We don't need to re-warm this cache after a device migration. - self.warmCaches() - } } private func logExpirationState() { @@ -61,41 +63,40 @@ public class AppExpiry: NSObject { } } - private func warmCaches() { + public func warmCaches(with tx: DBReadTransaction) { owsAssertDebug(GRDBSchemaMigrator.areMigrationsComplete) - if let persistedExpirationState: ExpirationState = databaseStorage.read(block: { transaction in - guard let persistedExpirationState: ExpirationState = try? self.keyValueStore.getCodableValue( - forKey: Self.expirationStateKey, - transaction: transaction - ) else { - return nil - } + let persistedExpirationState: ExpirationState? = try? self.keyValueStore.getCodableValue( + forKey: Self.keyValueKey, + transaction: tx + ) - // We only want to restore the persisted state if it's for our current version. - guard persistedExpirationState.version4 == AppVersion.shared.currentAppVersion4 else { - return nil - } - - return persistedExpirationState - }) { - expirationState.set(persistedExpirationState) + // We only want to restore the persisted state if it's for our current version. + guard + let persistedExpirationState, + persistedExpirationState.version4 == AppVersion.shared.currentAppVersion4 + else { + return } + expirationState.set(persistedExpirationState) + logExpirationState() } - private func updateExpirationState(_ state: ExpirationState) { + private func updateExpirationState(_ state: ExpirationState, db: DB) { expirationState.set(state) logExpirationState() - databaseStorage.asyncWrite { transaction in + db.asyncWrite { transaction in do { // Don't write or fire notification if the value hasn't changed. - if let oldState: ExpirationState = try self.keyValueStore.getCodableValue(forKey: Self.expirationStateKey, - transaction: transaction), - oldState == state { + let oldState: ExpirationState? = try self.keyValueStore.getCodableValue( + forKey: Self.keyValueKey, + transaction: transaction + ) + if let oldState, oldState == state { return } } catch { @@ -104,13 +105,14 @@ public class AppExpiry: NSObject { do { try self.keyValueStore.setCodable( state, - key: Self.expirationStateKey, + key: Self.keyValueKey, transaction: transaction ) } catch { owsFailDebug("Error persisting expiration state \(error)") } - transaction.addAsyncCompletionOffMain { + + transaction.addAsyncCompletion(on: self.schedulers.global()) { NotificationCenter.default.postNotificationNameAsync( Self.AppExpiryDidChange, object: nil @@ -119,28 +121,28 @@ public class AppExpiry: NSObject { } } - @objc - public func setHasAppExpiredAtCurrentVersion() { + public func setHasAppExpiredAtCurrentVersion(db: DB) { Logger.warn("") - updateExpirationState(ExpirationState(mode: .immediately)) + updateExpirationState(ExpirationState(mode: .immediately), db: db) } - @objc - public func setExpirationDateForCurrentVersion(_ newExpirationDate: Date?) { + public func setExpirationDateForCurrentVersion(_ newExpirationDate: Date?, db: DB) { guard !isExpired else { return owsFailDebug("Ignoring expiration date change for expired build.") } Logger.warn("\(String(describing: newExpirationDate))") + let newState: ExpirationState if let newExpirationDate = newExpirationDate { // Ignore any expiration date that is later than when the app expires by default. guard newExpirationDate < AppVersion.shared.defaultExpirationDate else { return } - updateExpirationState(ExpirationState(mode: .atDate, expirationDate: newExpirationDate)) + newState = .init(mode: .atDate, expirationDate: newExpirationDate) } else { - updateExpirationState(ExpirationState(mode: .default)) + newState = .init(mode: .default) } + updateExpirationState(newState, db: db) } @objc diff --git a/SignalServiceKit/src/Util/RemoteConfigManager.swift b/SignalServiceKit/src/Util/RemoteConfigManager.swift index 0295f5d77a..4e6e52cbf9 100644 --- a/SignalServiceKit/src/Util/RemoteConfigManager.swift +++ b/SignalServiceKit/src/Util/RemoteConfigManager.swift @@ -854,12 +854,13 @@ private extension ServiceRemoteConfigManager { if let minimumVersions = minimumVersions { Logger.info("Minimum client versions: \(minimumVersions)") + let db = DependenciesBridge.shared.db if let remoteExpirationDate = remoteExpirationDate(minimumVersions: minimumVersions) { Logger.info("Setting client expiration date: \(remoteExpirationDate)") - AppExpiry.shared.setExpirationDateForCurrentVersion(remoteExpirationDate) + AppExpiry.shared.setExpirationDateForCurrentVersion(remoteExpirationDate, db: db) } else { Logger.info("Clearing client expiration date") - AppExpiry.shared.setExpirationDateForCurrentVersion(nil) + AppExpiry.shared.setExpirationDateForCurrentVersion(nil, db: db) } } }