add cache
This commit is contained in:
parent
6fd82e25c1
commit
7c8940da5b
233
yobble/CoreData/ChatCacheWriter.swift
Normal file
233
yobble/CoreData/ChatCacheWriter.swift
Normal file
@ -0,0 +1,233 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -19,12 +19,14 @@ enum ChatServiceError: LocalizedError {
|
||||
final class ChatService {
|
||||
private let client: NetworkClient
|
||||
private let decoder: JSONDecoder
|
||||
private let cacheWriter: ChatCacheWriter
|
||||
|
||||
init(client: NetworkClient = .shared) {
|
||||
init(client: NetworkClient = .shared, cacheWriter: ChatCacheWriter = .shared) {
|
||||
self.client = client
|
||||
self.decoder = JSONDecoder()
|
||||
self.decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
self.decoder.dateDecodingStrategy = .custom(Self.decodeDate)
|
||||
self.cacheWriter = cacheWriter
|
||||
}
|
||||
|
||||
func fetchPrivateChats(
|
||||
@ -42,7 +44,7 @@ final class ChatService {
|
||||
method: .get,
|
||||
query: query,
|
||||
requiresAuth: true
|
||||
) { [decoder] result in
|
||||
) { [decoder, weak self] result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
do {
|
||||
@ -52,7 +54,9 @@ final class ChatService {
|
||||
completion(.failure(ChatServiceError.unexpectedStatus(message)))
|
||||
return
|
||||
}
|
||||
completion(.success(apiResponse.data))
|
||||
let chatData = apiResponse.data
|
||||
self?.cacheWriter.storePrivateChatList(chatData)
|
||||
completion(.success(chatData))
|
||||
} catch {
|
||||
let debugMessage = Self.describeDecodingError(error: error, data: response.data)
|
||||
if AppConfig.DEBUG {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user