Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Threema/Threema/   (Social Medium ©)  Datei vom 26.9.2026 mit Größe 7 kB image not shown  

Impressum AppLaunchTasks.swift   Sprache: JAVA

 

import CocoaLumberjackSwift
import FileUtility
import Foundation
import Keychain
import ThreemaEssentials
import ThreemaFramework

enum AppLaunchEvent {
    case didFinishLaunching
    case willEnterForeground
}

protocol AppLaunchTasksProtocol {
    func run(launchEvent: AppLaunchEvent)
}

final class AppLaunchTasks: AppLaunchTasksProtocol {

    private let businessInjector: BusinessInjectorProtocol
    private static var isRunning = false
    private static let isRunningQueue = DispatchQueue(label: "ch.threema.AppLaunchTasks.isRunningQueue")
    
    // Did the version or build change since the last launch? (This also detects changes between Store, TestFlight and
    // Xcode builds.)
    static var lastLaunchedVersionChanged: Bool {
        let lastVersion = AppGroup.userDefaults().string(forKey: "LastLaunchedAppVersionAndBuild")
        let currentVersion = ThreemaUtility.appAndBuildVersion
        
        guard let lastVersion else {
            DDLogNotice("Version has changed since last launch of app. Last: nil, current: \(currentVersion)")
            return true
        }
        
        if lastVersion != ThreemaUtility.appAndBuildVersion {
            DDLogNotice(
                "Version has changed since last launch of app. Last: \(lastVersion), current: \(currentVersion)"
            )
            return true
        }
        
        // No change
        return false
    }

    init(businessInjector: BusinessInjectorProtocol) {
        self.businessInjector = businessInjector
    }
    
    /// Runs some tasks/procedures when the App will be launched or will enter foreground. Especially DB repairing and
    /// checks must be run in the right order. But also other tasks can be started here, with the benefit this tasks
    /// will be run in serial.
    ///
    /// - Parameter launchEvent: App is launching or will enter foreground
    func run(launchEvent: AppLaunchEvent) {
        AppLaunchTasks.isRunningQueue.sync {
            guard !AppLaunchTasks.isRunning else {
                return
            }
            AppLaunchTasks.isRunning = true
            
            // Refresh dirty objects as early as possible
            let persistenceManager = PersistenceManager(
                appGroupID: AppGroup.groupID(),
                userDefaults: AppGroup.userDefaults(),
                remoteSecretManager: RemoteSecretProvider.remoteSecretManager
            )
            persistenceManager.dirtyObjectManager.refreshDirtyObjects(reset: true)
            
            switch launchEvent {
            case .didFinishLaunching:
                
                // Repairs database integrity only on app start and synchronously,
                // must be finished before running other tasks and returning to the caller
                businessInjector.entityManager.repairDatabaseIntegrity()

                // Delete all files and directories from temporary app directory
                FileUtility.shared.removeItemsInDirectory(directoryURL: FileUtility.shared.appTemporaryDirectory)
                
                // Reset MD setting if needed
                businessInjector.multiDeviceManager.resetEnableMultiDeviceIfNeeded()

                createPushNotificationEncryptionKeyIfNeeded()

                if AppLaunchTasks.lastLaunchedVersionChanged {
                    Task {
                        do {
                            try await AppUpdateSteps().run()
                            // Only persist last launched version if update steps were successful
                            AppLaunchTasks.updateLastLaunchedVersion()
                        }
                        catch {
                            DDLogWarn("Failed to run application update steps. Try again on next launch. \(error)")
                        }
                    }
                }
            case .willEnterForeground:
                // Validate RS if needed (& existing)
                RemoteSecretProvider.remoteSecretManager.checkValidity()
            }
            
            // Publish the microphone permission for the extensions, it might have been changed in the settings
            RecordPermission.storeCurrentAccess()

            checkLastMessageOfAllConversations()
            businessInjector.messageRetentionManager.deleteOldMessages()
            
            // All other tasks runs in a background thread
            Task {
                await businessInjector.runInBackground { businessInjector in
                    // This allows to disable multi-device if MD linking failed with a crash or if all other devices
                    // left the MD group
                    if launchEvent == .didFinishLaunching {
                        businessInjector.multiDeviceManager.disableMultiDeviceIfNeeded()
                    }
                    
                    AppLaunchTasks.isRunningQueue.async {
                        AppLaunchTasks.isRunning = false
                    }
                }
            }
        }
    }
    
    /// Create the key that the server uses to encrypt the payload of push notifications, if there is none
    ///
    /// Only the app may create it, because it is registered with the server when the push token is sent
    /// (`ServerConnector`). The extensions just read it to decrypt an incoming push.
    private func createPushNotificationEncryptionKeyIfNeeded() {
        do {
            guard try KeychainManager.loadPushNotificationEncryptionKey() == nil else {
                return
            }

            guard let key = BytesUtility.generateRandomBytes(length: Int(kNaClCryptoSymmKeySize)) else {
                DDLogError("Unable to create push notification encryption key")
                return
            }

            try KeychainManager.storePushNotificationEncryptionKey(key)

            DDLogNotice("Created push notification encryption key")
        }
        catch {
            DDLogError("Unable to create push notification encryption key: \(error)")
        }
    }

    private static func updateLastLaunchedVersion() {
        guard AppLaunchTasks.lastLaunchedVersionChanged else {
            return
        }
        let currentVersion = ThreemaUtility.appAndBuildVersion
        DDLogNotice("Update last launched version to: \(currentVersion)")
        AppGroup.userDefaults().setValue(currentVersion, forKey: "LastLaunchedAppVersionAndBuild")
    }

    /// Checks if the currently assigned last message of given Conversations is actually the correct one and fixes it
    /// if not (and recalculate count of unread messages for this conversation).
    private func checkLastMessageOfAllConversations() {
        var doUpdateUnreadMessagesCount = false

        businessInjector.entityManager.performAndWaitSave {
            guard let conversations = self.businessInjector.entityManager.entityFetcher
                .conversationEntities() else {
                return
            }

            for conversation in conversations {
                guard let effectiveLastMessage = MessageFetcher(
                    for: conversation,
                    with: self.businessInjector.entityManager
                ).lastDisplayMessage() else {
                    conversation.lastMessage = nil
                    continue
                }
                
                if conversation.lastMessage != effectiveLastMessage {
                    DDLogNotice(
                        "Assigned last message \(conversation.lastMessage?.id.hexString ?? "nil") did not equal effective last message \(effectiveLastMessage.id.hexString)"
                    )
                    conversation.lastMessage = effectiveLastMessage

                    self.businessInjector.unreadMessages.count(for: conversation)

                    doUpdateUnreadMessagesCount = true
                }
            }
        }

        if doUpdateUnreadMessagesCount {
            NotificationManager(businessInjector: businessInjector).updateUnreadMessagesCount()
        }
    }
}

Messung
CHZG
1009810099

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.