From ef5aa927a0ccfb8ae02b77a2bd75d9269c74134c Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 11 Aug 2023 18:48:14 +1000 Subject: [PATCH] Added logic to use the setting if it's already been sent in a config Added the ability to define requirements for migrations (in case some data or state needs to be loaded for a migration to be able to be performed correctly) --- Session.xcodeproj/project.pbxproj | 4 +++ .../_015_BlockCommunityMessageRequests.swift | 13 +++++++- .../Config Handling/SessionUtil+Shared.swift | 16 ++++++++++ .../SessionUtil+UserProfile.swift | 10 ++++++ SessionUtilitiesKit/Database/Storage.swift | 31 +++++++++++++++++++ .../Database/Types/Migration.swift | 4 +++ .../Database/Types/MigrationRequirement.swift | 12 +++++++ SignalUtilitiesKit/Utilities/AppSetup.swift | 16 ++++++++++ 8 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 SessionUtilitiesKit/Database/Types/MigrationRequirement.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 17a813494..a83905cf0 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -517,6 +517,7 @@ FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; }; FD1D732A2A85AA2000E3F410 /* Setting+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */; }; FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; }; + FD1F9C9F2A862BE60050F671 /* MigrationRequirement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */; }; FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.swift */; }; FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1E2A65269C0000B97C /* Crypto.swift */; }; FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */; }; @@ -1675,6 +1676,7 @@ FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = ""; }; FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Setting+Utilities.swift"; sourceTree = ""; }; FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = ""; }; + FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRequirement.swift; sourceTree = ""; }; FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = ""; }; FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+Crypto.swift"; sourceTree = ""; }; @@ -3671,6 +3673,7 @@ children = ( FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */, FD17D7B727F51ECA00122BE0 /* Migration.swift */, + FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */, FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */, FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */, FD37EA1028AB34B3003AE748 /* TypedTableAlteration.swift */, @@ -5678,6 +5681,7 @@ FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */, C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */, FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */, + FD1F9C9F2A862BE60050F671 /* MigrationRequirement.swift in Sources */, FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */, C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */, FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */, diff --git a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift b/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift index f887a6ce3..b512101b2 100644 --- a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift +++ b/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift @@ -10,6 +10,7 @@ enum _015_BlockCommunityMessageRequests: Migration { static let identifier: String = "BlockCommunityMessageRequests" static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.01 + static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded] static func migrate(_ db: Database) throws { // Add the new 'Profile' properties @@ -25,7 +26,17 @@ enum _015_BlockCommunityMessageRequests: Migration { Identity.userExists(db), (try Setting.exists(db, id: Setting.BoolKey.checkForCommunityMessageRequests.rawValue)) == false { - db[.checkForCommunityMessageRequests] = true + let rawBlindedMessageRequestValue: Int32 = try SessionUtil + .config(for: .userProfile, publicKey: getUserHexEncodedPublicKey(db)) + .wrappedValue + .map { conf -> Int32 in try SessionUtil.rawBlindedMessageRequestValue(in: conf) } + .defaulting(to: -1) + + // Use the value in the config if we happen to have one, otherwise use the default + db[.checkForCommunityMessageRequests] = (rawBlindedMessageRequestValue < 0 ? + true : + (rawBlindedMessageRequestValue > 0) + ) } Storage.update(progress: 1, for: self, in: target) // In case this is the last migration diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index ce2500f1c..4a9c8d286 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -210,6 +210,22 @@ internal extension SessionUtil { return updated } + static func hasSetting(_ db: Database, forKey key: String) throws -> Bool { + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Currently the only synced setting is 'checkForCommunityMessageRequests' + switch key { + case Setting.BoolKey.checkForCommunityMessageRequests.rawValue: + return try SessionUtil + .config(for: .userProfile, publicKey: userPublicKey) + .wrappedValue + .map { conf -> Bool in (try SessionUtil.rawBlindedMessageRequestValue(in: conf) >= 0) } + .defaulting(to: false) + + default: return false + } + } + static func updatingSetting(_ db: Database, _ updated: Setting?) throws { // Don't current support any nullable settings guard let updatedSetting: Setting = updated else { return } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift index 44ed7b2b2..4416eee82 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -186,3 +186,13 @@ internal extension SessionUtil { } } } + +// MARK: - Direct Values + +extension SessionUtil { + static func rawBlindedMessageRequestValue(in conf: UnsafeMutablePointer?) throws -> Int32 { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + return user_profile_get_blinded_msgreqs(conf) + } +} diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 464bf8eb5..2278e4670 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -47,8 +47,10 @@ open class Storage { fileprivate var dbWriter: DatabaseWriter? internal var testDbWriter: DatabaseWriter? { dbWriter } + private var unprocessedMigrationRequirements: Atomic<[MigrationRequirement]> = Atomic(MigrationRequirement.allCases) private var migrator: DatabaseMigrator? private var migrationProgressUpdater: Atomic<((String, CGFloat) -> ())>? + private var migrationRequirementProcesser: Atomic<(Database?, MigrationRequirement) -> ()>? // MARK: - Initialization @@ -77,6 +79,7 @@ open class Storage { migrationTargets: (customMigrationTargets ?? []), async: false, onProgressUpdate: nil, + onMigrationRequirement: { _, _ in }, onComplete: { _, _ in } ) return @@ -148,6 +151,7 @@ open class Storage { migrationTargets: [MigratableTarget.Type], async: Bool = true, onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, + onMigrationRequirement: @escaping (Database?, MigrationRequirement) -> (), onComplete: @escaping (Swift.Result, Bool) -> () ) { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { @@ -232,13 +236,24 @@ open class Storage { onProgressUpdate?(totalProgress, totalMinExpectedDuration) } }) + self.migrationRequirementProcesser = Atomic(onMigrationRequirement) // Store the logic to run when the migration completes let migrationCompleted: (Swift.Result) -> () = { [weak self] result in + // Process any unprocessed requirements which need to be processed before completion + // then clear out the state + self?.unprocessedMigrationRequirements.wrappedValue + .filter { $0.shouldProcessAtCompletionIfNotRequired } + .forEach { self?.migrationRequirementProcesser?.wrappedValue(nil, $0) } self?.migrationsCompleted.mutate { $0 = true } self?.migrationProgressUpdater = nil + self?.migrationRequirementProcesser = nil SUKLegacy.clearLegacyDatabaseInstance() + // Reset in case there is a requirement on a migration which runs when returning from + // the background + self?.unprocessedMigrationRequirements.mutate { $0 = MigrationRequirement.allCases } + // Don't log anything in the case of a 'success' or if the database is suspended (the // latter will happen if the user happens to return to the background too quickly on // launch so is unnecessarily alarming, it also gets caught and logged separately by @@ -288,6 +303,22 @@ open class Storage { } } + public func willStartMigration(_ db: Database, _ migration: Migration.Type) { + let unprocessedRequirements: Set = migration.requirements.asSet() + .intersection(unprocessedMigrationRequirements.wrappedValue.asSet()) + + // No need to do anything if there are no unprocessed requirements + guard !unprocessedRequirements.isEmpty else { return } + + // Process all of the requirements for this migration + unprocessedRequirements.forEach { migrationRequirementProcesser?.wrappedValue(db, $0) } + + // Remove any processed requirements from the list (don't want to process them multiple times) + unprocessedMigrationRequirements.mutate { + $0 = Array($0.asSet().subtracting(migration.requirements.asSet())) + } + } + public static func update( progress: CGFloat, for migration: Migration.Type, diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index 6e4c909e5..aa8a815de 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -8,17 +8,21 @@ public protocol Migration { static var identifier: String { get } static var needsConfigSync: Bool { get } static var minExpectedRunDuration: TimeInterval { get } + static var requirements: [MigrationRequirement] { get } static func migrate(_ db: Database) throws } public extension Migration { + static var requirements: [MigrationRequirement] { [] } + static func loggedMigrate( _ storage: Storage?, targetIdentifier: TargetMigrations.Identifier ) -> ((_ db: Database) throws -> ()) { return { (db: Database) in SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))") + storage?.willStartMigration(db, self) storage?.internalCurrentlyRunningMigration.mutate { $0 = (targetIdentifier, self) } defer { storage?.internalCurrentlyRunningMigration.mutate { $0 = nil } } diff --git a/SessionUtilitiesKit/Database/Types/MigrationRequirement.swift b/SessionUtilitiesKit/Database/Types/MigrationRequirement.swift new file mode 100644 index 000000000..9e586b809 --- /dev/null +++ b/SessionUtilitiesKit/Database/Types/MigrationRequirement.swift @@ -0,0 +1,12 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +import Foundation + +public enum MigrationRequirement: CaseIterable { + case sessionUtilStateLoaded + + var shouldProcessAtCompletionIfNotRequired: Bool { + switch self { + case .sessionUtilStateLoaded: return true + } + } +} diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 4ac031c56..75b708cba 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -82,6 +82,20 @@ public enum AppSetup { SNUIKit.self ], onProgressUpdate: migrationProgressChanged, + onMigrationRequirement: { db, requirement in + switch requirement { + case .sessionUtilStateLoaded: + guard Identity.userExists(db) else { return } + + // After the migrations have run but before the migration completion we load the + // SessionUtil state + SessionUtil.loadState( + db, + userPublicKey: getUserHexEncodedPublicKey(db), + ed25519SecretKey: Identity.fetchUserEd25519KeyPair(db)?.secretKey + ) + } + }, onComplete: { result, needsConfigSync in // After the migrations have run but before the migration completion we load the // SessionUtil state and update the 'needsConfigSync' flag based on whether the @@ -93,6 +107,8 @@ public enum AppSetup { ) } + // The 'needsConfigSync' flag should be based on whether either a migration or the + // configs need to be sync'ed migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) // The 'if' is only there to prevent the "variable never read" warning from showing