Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  EntityManager.swift   Sprache: JAVA

 

import CocoaLumberjackSwift
import CoreData
import Foundation
import ThreemaEssentials
import ThreemaProtocols

public final class EntityManager: NSObject {
    
    let dbContext: DatabaseContextProtocol
    static let getOrCreateContactQueue = DispatchQueue(label: "ch.threema.EntityManager.getOrCreateContactQueue")
    static let getOrCreateConversationQueue =
        DispatchQueue(label: "ch.threema.EntityManager.getOrCreateConversationQueue")
    static let getOrCreateMessageQueue = DispatchQueue(label: "ch.threema.EntityManager.getOrCreateMessageQueue")

    @objc public let entityCreator: EntityCreator
    @objc public let entityFetcher: EntityFetcher
    @objc public let entityDestroyer: EntityDestroyer
    
    let isRemoteSecretEnabled: Bool
    
    // MARK: - Lifecycle

    @objc init(databaseContext: DatabaseContextProtocol, isRemoteSecretEnabled: Bool) {
        self.dbContext = databaseContext
        self.entityCreator = EntityCreator(managedObjectContext: dbContext.current)
        self.entityFetcher = EntityFetcher(
            managedObjectContext: dbContext.current
        )
        self.entityDestroyer = EntityDestroyer(
            managedObjectContext: dbContext.current
        )
        
        self.isRemoteSecretEnabled = isRemoteSecretEnabled
        
        super.init()
    }
    
    // MARK: - General actions

    public var hasBackgroundChildContext: Bool {
        dbContext.current !== dbContext.main
    }

    public func isEqualWithCurrentContext(managedObjectContext: NSManagedObjectContext) -> Bool {
        dbContext.current === managedObjectContext
    }

    @objc public func perform(_ block: @escaping () -> Void) {
        dbContext.current.perform {
            block()
        }
    }

    @objc public func performAndWait(_ block: @escaping () -> Void) {
        dbContext.current.performAndWait {
            block()
        }
    }

    @objc public func performSave(_ block: @escaping () -> Void) {
        dbContext.current.perform {
            block()
            try? self.internalSave()
        }
    }

    @objc public func performAndWaitSave(_ block: @escaping () -> Void) {
        dbContext.current.performAndWait {
            block()
            try? internalSave()
        }
    }

    public func performAndWaitSave<T>(_ block: @escaping () throws -> T) rethrows -> T {
        try dbContext.current.performAndWait {
            let returnValue = try block()
            try self.internalSave()
            return returnValue
        }
    }

    public func performSave<T>(_ block: @escaping () throws -> T) async rethrows -> T {
        try await dbContext.current.perform(schedule: .immediate) {
            let returnValue = try block()
            try self.internalSave()
            return returnValue
        }
    }

    public func performAndWait<T>(_ block: @escaping () throws -> T) rethrows -> T {
        try dbContext.current.performAndWait {
            try block()
        }
    }

    public func perform<T>(_ block: @escaping () throws -> T) async rethrows -> T {
        try await dbContext.current.perform(schedule: .immediate) {
            try block()
        }
    }

    // Save the current context it has changes and
    // the main context is the current context a child context.
    public func save() throws {
        try internalSave()
    }

    // Rollback the current DB context
    public func rollback() {
        dbContext.current.rollback()
    }
    
    // Rollback the current and the main DB context
    public func fullRollback() {
        dbContext.current.rollback()
        dbContext.main.rollback()
    }
    
    // MARK: - Managed Object Context functions

    public var isMainDBContext: Bool {
        dbContext.main === dbContext.current
    }

    /// Convert URL serialized managed object ID to `NSManagedObjectID`
    /// - Parameter url: URL to convert
    /// - Returns: `NSManagedObjectID` if it was found in the persistent store
    public func managedObjectID(forURIRepresentation url: URL) -> NSManagedObjectID? {
        dbContext.current.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: url)
    }

    // Refresh all NSManagedObject on current context.
    public func refreshAllObjects() {
        let stalenessInterval: TimeInterval = dbContext.current.stalenessInterval
        dbContext.current.stalenessInterval = 0.0
        dbContext.current.refreshAllObjects()
        dbContext.current.stalenessInterval = stalenessInterval
    }
    
    /// Refresh NSManagedObject on current context.
    ///
    /// - Parameter object: NSManagedObject
    /// - Parameter mergeChanges: Bool
    public func refresh(_ object: NSManagedObject?, mergeChanges: Bool) {
        guard let object else {
            return
        }
        
        performAndWait {
            let stalenessInterval: TimeInterval = self.dbContext.current.stalenessInterval
            self.dbContext.current.stalenessInterval = 0.0
            self.dbContext.current.refresh(object, mergeChanges: mergeChanges)
            self.dbContext.current.stalenessInterval = stalenessInterval
        }
    }
}

// MARK: - Data access & creation helpers, convenience functions

extension EntityManager {

    /// Check and repair database integrity at the moment just the relationship of `Conversation.lastMessage`.
    public func repairDatabaseIntegrity() {
        performAndWaitSave {
            guard let conversations = self.entityFetcher.conversationEntitiesWithBrokenLastMessage() else {
                return
            }

            for conversation in conversations {
                conversation.lastMessage = nil
            }
        }
    }

    /// Remove contact entities
    /// Assumption: the contacts have no messages and no conversations
    public func cleanupUnusedContacts(_ contacts: [ContactEntity]) {
        performAndWaitSave {
            for contact in contacts {
                self.dbContext.current.delete(contact)
            }
        }
    }

    /// Check (on main thread and main DB context) is message nonce already in DB.
    ///
    /// This is useful during incoming message processing to check
    /// if a message is already processed to prevent race conditions
    /// between App and Notification Extension.
    ///
    /// - Parameter hashedNonce: Hashed Message Nonce to check
    /// - Returns: True nonce found in DB
    public func isMessageNonceAlreadyInDB(_ hashedNonce: Data) -> Bool {
        var isProcessed = false

        let isNonceAlreadyInDB: (Data) -> Void = { _ in
            let entityFetcherOnMain = EntityFetcher(
                managedObjectContext: self.dbContext.main
            )

            self.dbContext.main.performAndWait {
                isProcessed = entityFetcherOnMain.isNonceEntityAlreadyInDB(hashedNonce)
            }
        }

        if Thread.isMainThread {
            isNonceAlreadyInDB(hashedNonce)
        }
        else {
            DispatchQueue.main.sync {
                isNonceAlreadyInDB(hashedNonce)
            }
        }
        return isProcessed
    }

    /// Set sent and sent date of outgoing message.
    ///
    /// - Parameters:
    /// - messageID: ID of message that was sent
    /// - conversation: Conversation of the message
    /// - sentAt: Sent date is reflected at or now if message was not reflected
    /// - isLocal: True means message was NOT sent to the chat server
    public func markMessageAsSent(
        _ messageID: Data,
        in conversation: ConversationEntity,
        sentAt: Date = .now,
        isLocal: Bool = false
    ) {
        performAndWaitSave {
            if let dbMsg = self.entityFetcher.message(with: messageID, in: conversation, isOwn: true),
               let sent = Bool(exactly: dbMsg.sent), !sent {
                dbMsg.sent = true
                dbMsg.sendFailed = false

                // Only set remote sent date if it was actually sent to the chat server
                if !isLocal {
                    dbMsg.remoteSentDate = sentAt
                }
            }
        }
    }

    /// Set forward security mode of own message and save.
    ///
    /// - Parameters:
    /// - messageID: Message to set FS mode on
    /// - conversation: Conversation of the message
    /// - forwardSecurityMode: new mode
    public func setForwardSecurityMode(
        _ messageID: Data,
        in conversation: ConversationEntity,
        forwardSecurityMode: BaseMessageEntity.ForwardSecurityMode
    ) {
        performAndWaitSave {
            guard let dbMsg = self.entityFetcher.message(with: messageID, in: conversation, isOwn: true) else {
                // This might also be called for messages not stored in CD. Thus we don't log anything here.
                return
            }

            dbMsg.forwardSecurityMode = forwardSecurityMode.rawValue as NSNumber
        }
    }

    /// Remove contacts form rejected-by list of passed message
    ///
    /// This is normally only needed for group messages
    ///
    /// - Parameters:
    ///   - contactIDs: Threema ID strings of IDs to remove
    ///   - messageID: Message ID of message to remove receivers from
    ///   - conversation: Conversation the message is in
    public func removeContacts(
        with contactIDs: Set<String>,
        fromRejectedListOfMessageWith messageID: Data,
        in conversation: ConversationEntity
    ) {
        performAndWaitSave {
            guard let dbMsg = self.entityFetcher.message(with: messageID, in: conversation, isOwn: true) else {
                DDLogWarn("No own message to be found for \(messageID)")
                return
            }

            // Only remove all possible contacts if there are any rejected
            guard !(dbMsg.rejectedBy?.isEmpty ?? true) else {
                return
            }

            for contactID in contactIDs {
                // For example if your own ID happens to be in contactIDs the contact cannot be loaded
                guard let contact = self.entityFetcher.contactEntity(for: contactID) else {
                    continue
                }

                dbMsg.removeFromRejectedBy(contact)
            }
        }
    }
}

// MARK: - Private Functions

extension EntityManager {
    private func internalSave() throws {
        guard dbContext.current.hasChanges else {
            return
        }
        
        // Fixes Crash when swipe-deleting Conversation on iOS15b8
        #if DEBUG
            DDLogVerbose("inserted objects: \(dbContext.current.insertedObjects)")
            DDLogVerbose("updated objects: \(dbContext.current.updatedObjects)")
            // For deleted objects log only objectID, otherwise it crash when logging for a none optional field on
            // encrypted DB
            DDLogVerbose("deleted objects: \(dbContext.current.deletedObjects.map(\.objectID))")
        #endif
        
        // Workaround for temporary managed object IDs
        //
        // After creating a managed object it appears in the context it is created on with a temporary object ID
        // (indicated by the "t" at the beginning of the UUID of a printed object ID). This object ID stays temporary
        // until the object is saved to a persistent store. Thus when a child contexts is saved all new managed objects
        // appear with their temporary object ID in the parent context.
        // A fetched results controller (FRC) on a context picks this up and sends an update (e.g. the one used in the
        // new chat view) with the temporary object IDs if you use diffable DS. However, there is no snapshot update
        // when the temporary object ID is switched out for a permanent one unless you update and save the object one
        // more time. Depending on the implementation this leads to a crash due to an unknown (temporary) object ID in
        // the DS.
        //
        // Workaround: We prefetch the permanent object IDs of all inserted objects before every save. Note: Testing
        // showed that we have to do this also if there is no parent context to work as expected.
        //
        // This should be addressed on a lower level with IOS-2354. Probably by replacing child contexts by multiple
        // contexts accessing the same permanent store directly.
        //
        // Sources:
        // - https://developer.apple.com/forums/thread/692357?answerID=691521022#691521022
        // - https://stackoverflow.com/q/11336120
        do {
            try dbContext.current.obtainPermanentIDs(for: Array(dbContext.current.insertedObjects))
        }
        catch {
            DDLogWarn("Unable to obtain permanent ids: \(error)")
        }
        
        var success = false
        do {
            try dbContext.current.save()
            
            success = true
        }
        catch {
            DDLogError("Error saving current context: \(error)")

            if ProcessInfoHelper.isRunningForTests {
                throw error
            }

            NotificationCenter.default.post(
                name: DatabaseContext.errorWhileProcessingManagedObject,
                object: nil,
                userInfo: [DatabaseContext.errorKey: error]
            )
        }
        
        if success, dbContext.current.parent != nil {
            // Save parent context (changes were pushed by save in child context)
            do {
                try dbContext.main.performAndWait {
                    try self.dbContext.main.save()
                }
            }
            catch {
                DDLogError("Error saving main context: \(error)")

                if ProcessInfoHelper.isRunningForTests {
                    throw error
                }

                NotificationCenter.default.post(
                    name: DatabaseContext.errorWhileProcessingManagedObject,
                    object: nil,
                    userInfo: [DatabaseContext.errorKey: error]
                )
            }
        }
    }
}

Messung
CHZG
829710093

¤ 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104845
#Domains=499266