234 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
import Foundation
 | 
						|
import CoreData
 | 
						|
 | 
						|
final class ChatCacheWriter {
 | 
						|
    static let shared = ChatCacheWriter()
 | 
						|
 | 
						|
    private let persistenceController: PersistenceController
 | 
						|
    private let userDefaults: UserDefaults
 | 
						|
    private let keychainService: KeychainService
 | 
						|
 | 
						|
    init(
 | 
						|
        persistenceController: PersistenceController = .shared,
 | 
						|
        userDefaults: UserDefaults = .standard,
 | 
						|
        keychainService: KeychainService = .shared
 | 
						|
    ) {
 | 
						|
        self.persistenceController = persistenceController
 | 
						|
        self.userDefaults = userDefaults
 | 
						|
        self.keychainService = keychainService
 | 
						|
    }
 | 
						|
 | 
						|
    func storePrivateChatList(_ data: PrivateChatListData) {
 | 
						|
        guard !AppConfig.DISABLE_DB else { return }
 | 
						|
        guard let accountUserId = resolveCurrentAccountUserId() else { return }
 | 
						|
 | 
						|
        let context = persistenceController.newBackgroundContext()
 | 
						|
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
 | 
						|
 | 
						|
        context.perform { [weak self] in
 | 
						|
            guard let self else { return }
 | 
						|
            do {
 | 
						|
                try self.sync(chatList: data, accountUserId: accountUserId, context: context)
 | 
						|
                if context.hasChanges {
 | 
						|
                    try context.save()
 | 
						|
                }
 | 
						|
            } catch {
 | 
						|
                if AppConfig.DEBUG {
 | 
						|
                    print("[ChatCacheWriter] Failed to store private chat list: \(error)")
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private func resolveCurrentAccountUserId() -> String? {
 | 
						|
        guard let login = userDefaults.string(forKey: "currentUser") else {
 | 
						|
            return nil
 | 
						|
        }
 | 
						|
        return keychainService.get(forKey: "userId", service: login)
 | 
						|
    }
 | 
						|
 | 
						|
    private func sync(chatList: PrivateChatListData, accountUserId: String, context: NSManagedObjectContext) throws {
 | 
						|
        var profileCache: [String: CDProfile] = [:]
 | 
						|
        var chatCache: [String: CDPrivateChat] = [:]
 | 
						|
        var messageCache: [String: CDMessage] = [:]
 | 
						|
 | 
						|
        for item in chatList.items {
 | 
						|
            let chat = try upsertChat(item, accountUserId: accountUserId, context: context, cache: &chatCache)
 | 
						|
            let previousLastMessage = chat.lastMessage
 | 
						|
 | 
						|
            if let profileData = item.chatData {
 | 
						|
                let profile = try upsertProfile(profileData, accountUserId: accountUserId, context: context, cache: &profileCache)
 | 
						|
                chat.chatData = profile
 | 
						|
            } else {
 | 
						|
                chat.chatData = nil
 | 
						|
            }
 | 
						|
 | 
						|
            if let lastMessageData = item.lastMessage {
 | 
						|
                let message = try upsertMessage(
 | 
						|
                    lastMessageData,
 | 
						|
                    accountUserId: accountUserId,
 | 
						|
                    chat: chat,
 | 
						|
                    context: context,
 | 
						|
                    profileCache: &profileCache,
 | 
						|
                    messageCache: &messageCache
 | 
						|
                )
 | 
						|
                if let previous = previousLastMessage, previous.objectID != message.objectID {
 | 
						|
                    previous.chatAsLastMessage = nil
 | 
						|
                }
 | 
						|
                chat.lastMessage = message
 | 
						|
            } else {
 | 
						|
                previousLastMessage?.chatAsLastMessage = nil
 | 
						|
                chat.lastMessage = nil
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private func upsertChat(
 | 
						|
        _ chatItem: PrivateChatListItem,
 | 
						|
        accountUserId: String,
 | 
						|
        context: NSManagedObjectContext,
 | 
						|
        cache: inout [String: CDPrivateChat]
 | 
						|
    ) throws -> CDPrivateChat {
 | 
						|
        if let cached = cache[chatItem.chatId] {
 | 
						|
            apply(chatItem: chatItem, to: cached, accountUserId: accountUserId)
 | 
						|
            return cached
 | 
						|
        }
 | 
						|
 | 
						|
        let chat = try fetchChat(chatId: chatItem.chatId, accountUserId: accountUserId, context: context) ?? CDPrivateChat(context: context)
 | 
						|
        apply(chatItem: chatItem, to: chat, accountUserId: accountUserId)
 | 
						|
        cache[chatItem.chatId] = chat
 | 
						|
        return chat
 | 
						|
    }
 | 
						|
 | 
						|
    private func apply(chatItem: PrivateChatListItem, to chat: CDPrivateChat, accountUserId: String) {
 | 
						|
        chat.accountUserId = accountUserId
 | 
						|
        chat.chatId = chatItem.chatId
 | 
						|
        chat.chatType = chatItem.chatType.rawValue
 | 
						|
        chat.createdAt = chatItem.createdAt
 | 
						|
        chat.unreadCount = Int32(chatItem.unreadCount)
 | 
						|
    }
 | 
						|
 | 
						|
    private func upsertProfile(
 | 
						|
        _ profileData: ChatProfile,
 | 
						|
        accountUserId: String,
 | 
						|
        context: NSManagedObjectContext,
 | 
						|
        cache: inout [String: CDProfile]
 | 
						|
    ) throws -> CDProfile {
 | 
						|
        if let cached = cache[profileData.userId] {
 | 
						|
            apply(profile: profileData, to: cached, accountUserId: accountUserId)
 | 
						|
            return cached
 | 
						|
        }
 | 
						|
 | 
						|
        let profile = try fetchProfile(userId: profileData.userId, accountUserId: accountUserId, context: context) ?? CDProfile(context: context)
 | 
						|
        apply(profile: profileData, to: profile, accountUserId: accountUserId)
 | 
						|
        cache[profileData.userId] = profile
 | 
						|
        return profile
 | 
						|
    }
 | 
						|
 | 
						|
    private func apply(profile profileData: ChatProfile, to profile: CDProfile, accountUserId: String) {
 | 
						|
        profile.accountUserId = accountUserId
 | 
						|
        profile.userId = profileData.userId
 | 
						|
        profile.login = profileData.login
 | 
						|
        profile.fullName = profileData.fullName
 | 
						|
        profile.customName = profileData.customName
 | 
						|
        profile.bio = profileData.bio
 | 
						|
        if let lastSeen = profileData.lastSeen {
 | 
						|
            profile.setValue(NSNumber(value: lastSeen), forKey: "lastSeen")
 | 
						|
        } else {
 | 
						|
            profile.setValue(nil, forKey: "lastSeen")
 | 
						|
        }
 | 
						|
        profile.createdAt = profileData.createdAt
 | 
						|
        profile.isOfficial = profileData.isOfficial
 | 
						|
    }
 | 
						|
 | 
						|
    private func upsertMessage(
 | 
						|
        _ messageData: MessageItem,
 | 
						|
        accountUserId: String,
 | 
						|
        chat: CDPrivateChat,
 | 
						|
        context: NSManagedObjectContext,
 | 
						|
        profileCache: inout [String: CDProfile],
 | 
						|
        messageCache: inout [String: CDMessage]
 | 
						|
    ) throws -> CDMessage {
 | 
						|
        if let cached = messageCache[messageData.messageId] {
 | 
						|
            apply(message: messageData, to: cached, accountUserId: accountUserId, chat: chat, context: context, profileCache: &profileCache)
 | 
						|
            return cached
 | 
						|
        }
 | 
						|
 | 
						|
        let message = try fetchMessage(messageId: messageData.messageId, accountUserId: accountUserId, context: context) ?? CDMessage(context: context)
 | 
						|
        apply(message: messageData, to: message, accountUserId: accountUserId, chat: chat, context: context, profileCache: &profileCache)
 | 
						|
        messageCache[messageData.messageId] = message
 | 
						|
        return message
 | 
						|
    }
 | 
						|
 | 
						|
    private func apply(
 | 
						|
        message messageData: MessageItem,
 | 
						|
        to message: CDMessage,
 | 
						|
        accountUserId: String,
 | 
						|
        chat: CDPrivateChat,
 | 
						|
        context: NSManagedObjectContext,
 | 
						|
        profileCache: inout [String: CDProfile]
 | 
						|
    ) {
 | 
						|
        message.accountUserId = accountUserId
 | 
						|
        message.messageId = messageData.messageId
 | 
						|
        message.chatId = messageData.chatId
 | 
						|
        message.messageType = messageData.messageType
 | 
						|
        message.content = messageData.content
 | 
						|
        message.mediaLink = messageData.mediaLink
 | 
						|
        message.createdAt = messageData.createdAt
 | 
						|
        message.updatedAt = messageData.updatedAt
 | 
						|
        message.senderId = messageData.senderId
 | 
						|
        message.chat = chat
 | 
						|
        message.chatAsLastMessage = chat
 | 
						|
 | 
						|
        if let isViewed = messageData.isViewed {
 | 
						|
            message.setValue(NSNumber(value: isViewed), forKey: "isViewed")
 | 
						|
        } else {
 | 
						|
            message.setValue(nil, forKey: "isViewed")
 | 
						|
        }
 | 
						|
 | 
						|
        if let forward = messageData.forwardMetadata {
 | 
						|
            message.forwardType = forward.forwardType
 | 
						|
            message.forwardSenderId = forward.forwardSenderId
 | 
						|
            message.forwardMessageId = forward.forwardMessageId
 | 
						|
        } else {
 | 
						|
            message.forwardType = nil
 | 
						|
            message.forwardSenderId = nil
 | 
						|
            message.forwardMessageId = nil
 | 
						|
        }
 | 
						|
 | 
						|
        if let senderProfileData = messageData.senderData {
 | 
						|
            if let profile = try? upsertProfile(
 | 
						|
                senderProfileData,
 | 
						|
                accountUserId: accountUserId,
 | 
						|
                context: context,
 | 
						|
                cache: &profileCache
 | 
						|
            ) {
 | 
						|
                message.sender = profile
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            message.sender = nil
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private func fetchChat(chatId: String, accountUserId: String, context: NSManagedObjectContext) throws -> CDPrivateChat? {
 | 
						|
        let request = NSFetchRequest<CDPrivateChat>(entityName: "CDPrivateChat")
 | 
						|
        request.fetchLimit = 1
 | 
						|
        request.predicate = NSPredicate(format: "accountUserId == %@ AND chatId == %@", accountUserId, chatId)
 | 
						|
        return try context.fetch(request).first
 | 
						|
    }
 | 
						|
 | 
						|
    private func fetchProfile(userId: String, accountUserId: String, context: NSManagedObjectContext) throws -> CDProfile? {
 | 
						|
        let request = NSFetchRequest<CDProfile>(entityName: "CDProfile")
 | 
						|
        request.fetchLimit = 1
 | 
						|
        request.predicate = NSPredicate(format: "accountUserId == %@ AND userId == %@", accountUserId, userId)
 | 
						|
        return try context.fetch(request).first
 | 
						|
    }
 | 
						|
 | 
						|
    private func fetchMessage(messageId: String, accountUserId: String, context: NSManagedObjectContext) throws -> CDMessage? {
 | 
						|
        let request = NSFetchRequest<CDMessage>(entityName: "CDMessage")
 | 
						|
        request.fetchLimit = 1
 | 
						|
        request.predicate = NSPredicate(format: "accountUserId == %@ AND messageId == %@", accountUserId, messageId)
 | 
						|
        return try context.fetch(request).first
 | 
						|
    }
 | 
						|
}
 |