// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import GRDB import SessionMessagingKit import SessionUtilitiesKit import SessionUIKit import SessionSnodeKit public enum AppSetup { private static let hasRun: Atomic = Atomic(false) public static func setupEnvironment( retrySetupIfDatabaseInvalid: Bool = false, appSpecificBlock: @escaping () -> (), migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result, Bool) -> () ) { // If we've already run the app setup then only continue under certain circumstances guard !AppSetup.hasRun.wrappedValue else { let storageIsValid: Bool = Storage.shared.isValid switch (retrySetupIfDatabaseInvalid, storageIsValid) { case (true, false): Storage.reconfigureDatabase() AppSetup.hasRun.mutate { $0 = false } AppSetup.setupEnvironment( retrySetupIfDatabaseInvalid: false, // Don't want to get stuck in a loop appSpecificBlock: appSpecificBlock, migrationProgressChanged: migrationProgressChanged, migrationsCompletion: migrationsCompletion ) default: migrationsCompletion( (storageIsValid ? .success(()) : .failure(StorageError.startupFailed)), false ) } return } AppSetup.hasRun.mutate { $0 = true } var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(labelStr: #function) DispatchQueue.global(qos: .userInitiated).async { // Order matters here. // // All of these "singletons" should have any dependencies used in their // initializers injected. OWSBackgroundTaskManager.shared().observeNotifications() // Attachments can be stored to NSTemporaryDirectory() // If you receive a media message while the device is locked, the download will fail if // the temporary directory is NSFileProtectionComplete let success: Bool = OWSFileSystem.protectFileOrFolder( atPath: NSTemporaryDirectory(), fileProtectionType: .completeUntilFirstUserAuthentication ) assert(success) Environment.shared = Environment( reachabilityManager: SSKReachabilityManagerImpl(), audioSession: OWSAudioSession(), proximityMonitoringManager: OWSProximityMonitoringManagerImpl(), windowManager: OWSWindowManager(default: ()) ) appSpecificBlock() /// `performMainSetup` **MUST** run before `perform(migrations:)` Configuration.performMainSetup() runPostSetupMigrations( backgroundTask: backgroundTask, migrationProgressChanged: migrationProgressChanged, migrationsCompletion: migrationsCompletion ) // The 'if' is only there to prevent the "variable never read" warning from showing if backgroundTask != nil { backgroundTask = nil } } } public static func runPostSetupMigrations( backgroundTask: OWSBackgroundTask? = nil, migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result, Bool) -> () ) { var backgroundTask: OWSBackgroundTask? = (backgroundTask ?? OWSBackgroundTask(labelStr: #function)) Storage.shared.perform( migrationTargets: [ SNUtilitiesKit.self, SNSnodeKit.self, SNMessagingKit.self, 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 // 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 if backgroundTask != nil { backgroundTask = nil } } ) } }