fix performance

This commit is contained in:
fmodf 2024-08-07 14:49:47 +02:00
parent 794c50fed0
commit 889211683b
10 changed files with 611 additions and 507 deletions

View file

@ -13,6 +13,7 @@ final class Store<State: Stateable, Action: Codable>: ObservableObject {
// Serial queue for performing any actions sequentially // Serial queue for performing any actions sequentially
private let serialQueue = DispatchQueue(label: "im.narayana.conversations.classic.serial.queue", qos: .userInteractive) private let serialQueue = DispatchQueue(label: "im.narayana.conversations.classic.serial.queue", qos: .userInteractive)
private let middlewareQueue = DispatchQueue(label: "im.narayana.conversations.classic.middleware.queue", qos: .default, attributes: .concurrent)
private let reducer: Reducer<State, Action> private let reducer: Reducer<State, Action>
private let middlewares: [Middleware<State, Action>] private let middlewares: [Middleware<State, Action>]
@ -71,8 +72,11 @@ final class Store<State: Stateable, Action: Codable>: ObservableObject {
} }
startTime = CFAbsoluteTimeGetCurrent() startTime = CFAbsoluteTimeGetCurrent()
middleware middleware
.subscribe(on: middlewareQueue)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink(receiveValue: dispatch) .sink(receiveValue: { [weak self] action in
self?.dispatch(action)
})
.store(in: &middlewareCancellables) .store(in: &middlewareCancellables)
timeElapsed = CFAbsoluteTimeGetCurrent() - startTime timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
if timeElapsed > 0.05 { if timeElapsed > 0.05 {

View file

@ -22,7 +22,8 @@ final class AccountsMiddleware {
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.clientConnectionChanged(let jid, let connectionStatus)): case .xmppAction(.clientConnectionChanged(let jid, let connectionStatus)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
guard let account = state.accountsState.accounts.first(where: { $0.bareJid == jid }) else { guard let account = state.accountsState.accounts.first(where: { $0.bareJid == jid }) else {
promise(.success(.info("AccountsMiddleware: account not found for jid \(jid)"))) promise(.success(.info("AccountsMiddleware: account not found for jid \(jid)")))
return return
@ -46,16 +47,19 @@ final class AccountsMiddleware {
promise(.success(.info("AccountsMiddleware: account \(jid) is not temporary, ignoring"))) promise(.success(.info("AccountsMiddleware: account \(jid) is not temporary, ignoring")))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.serverFeaturesLoaded(let jid, let features)): case .xmppAction(.serverFeaturesLoaded(let jid, let features)):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
let serverFeatures = features let serverFeatures = features
.compactMap { featureId in .compactMap { featureId in
self?.allFeatures.first(where: { $0.xmppId == featureId }) self?.allFeatures.first(where: { $0.xmppId == featureId })
} }
promise(.success(.accountsAction(.clientServerFeaturesUpdated(jid: jid, features: serverFeatures)))) promise(.success(.accountsAction(.clientServerFeaturesUpdated(jid: jid, features: serverFeatures))))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
default: default:

View file

@ -10,7 +10,8 @@ final class ChatsMiddleware {
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .chatsAction(.startChat(accountJid: let accountJid, participantJid: let participantJid)): case .chatsAction(.startChat(accountJid: let accountJid, participantJid: let participantJid)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
if let exist = state.chatsState.chats.first(where: { $0.account == accountJid && $0.participant == participantJid }) { if let exist = state.chatsState.chats.first(where: { $0.account == accountJid && $0.participant == participantJid }) {
// open existing chat // open existing chat
promise(.success(.chatsAction(.chatStarted(chat: exist)))) promise(.success(.chatsAction(.chatStarted(chat: exist))))
@ -19,6 +20,7 @@ final class ChatsMiddleware {
promise(.success(.chatsAction(.createNewChat(accountJid: accountJid, participantJid: participantJid)))) promise(.success(.chatsAction(.createNewChat(accountJid: accountJid, participantJid: participantJid))))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .chatsAction(.chatCreated(let chat)): case .chatsAction(.chatCreated(let chat)):

View file

@ -6,11 +6,13 @@ final class ConversationMiddleware {
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> { func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action { switch action {
case .chatsAction(.chatStarted(let chat)): case .chatsAction(.chatStarted(let chat)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
let roster = state.rostersState.rosters let roster = state.rostersState.rosters
.first { $0.bareJid == chat.account && $0.contactBareJid == chat.participant } .first { $0.bareJid == chat.account && $0.contactBareJid == chat.participant }
promise(.success(.conversationAction(.makeConversationActive(chat: chat, roster: roster)))) promise(.success(.conversationAction(.makeConversationActive(chat: chat, roster: roster))))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .conversationAction(.makeConversationActive): case .conversationAction(.makeConversationActive):

View file

@ -40,7 +40,8 @@ final class DatabaseMiddleware {
switch action { switch action {
// MARK: Accounts // MARK: Accounts
case .startAction(.loadStoredAccounts): case .startAction(.loadStoredAccounts):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.loadingStoredAccountsFailed))) promise(.success(.databaseAction(.loadingStoredAccountsFailed)))
@ -56,10 +57,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .accountsAction(.makeAccountPermanent(let account)): case .accountsAction(.makeAccountPermanent(let account)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.updateAccountFailed))) promise(.success(.databaseAction(.updateAccountFailed)))
@ -83,11 +86,13 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
// MARK: Rosters // MARK: Rosters
case .rostersAction(.markRosterAsLocallyDeleted(let ownerJID, let contactJID)): case .rostersAction(.markRosterAsLocallyDeleted(let ownerJID, let contactJID)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError)))) promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError))))
@ -106,10 +111,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .rostersAction(.unmarkRosterAsLocallyDeleted(let ownerJID, let contactJID)): case .rostersAction(.unmarkRosterAsLocallyDeleted(let ownerJID, let contactJID)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError)))) promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError))))
@ -128,11 +135,13 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
// MARK: Chats // MARK: Chats
case .chatsAction(.createNewChat(let accountJid, let participantJid)): case .chatsAction(.createNewChat(let accountJid, let participantJid)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.chatsAction(.chatCreationFailed(reason: L10n.Global.Error.genericDbError)))) promise(.success(.chatsAction(.chatCreationFailed(reason: L10n.Global.Error.genericDbError))))
@ -154,6 +163,7 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
// MARK: Conversation and messages // MARK: Conversation and messages
@ -162,7 +172,8 @@ final class DatabaseMiddleware {
return Empty().eraseToAnyPublisher() return Empty().eraseToAnyPublisher()
case .xmppAction(.xmppMessageReceived(let message)): case .xmppAction(.xmppMessageReceived(let message)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError)))) promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))))
@ -182,10 +193,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .conversationAction(.sendMessage(let from, let to, let body)): case .conversationAction(.sendMessage(let from, let to, let body)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError)))) promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))))
@ -215,11 +228,13 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.xmppMessageSendSuccess(let msgId)): case .xmppAction(.xmppMessageSendSuccess(let msgId)):
// mark message as pending false and sentError false // mark message as pending false and sentError false
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError)))) promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))))
@ -238,11 +253,13 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.xmppMessageSendFailed(let msgId)): case .xmppAction(.xmppMessageSendFailed(let msgId)):
// mark message as pending false and sentError true // mark message as pending false and sentError true
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError)))) promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))))
@ -260,11 +277,13 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
// MARK: Attachments // MARK: Attachments
case .fileAction(.downloadAttachmentFile(let id, _)): case .fileAction(.downloadAttachmentFile(let id, _)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError)))
@ -284,10 +303,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.downloadingAttachmentFileFailed(let id, _)): case .fileAction(.downloadingAttachmentFileFailed(let id, _)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError)))
@ -307,10 +328,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.attachmentFileDownloaded(let id, let localName)): case .fileAction(.attachmentFileDownloaded(let id, let localName)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError)))
@ -330,10 +353,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.attachmentThumbnailCreated(let id, let thumbnailName)): case .fileAction(.attachmentThumbnailCreated(let id, let thumbnailName)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.updateAttachmentFailed(id: id, reason: L10n.Global.Error.genericDbError)))
@ -353,11 +378,13 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
// MARK: Sharing // MARK: Sharing
case .conversationAction(.sendMediaMessages(let from, let to, let messageIds, let localFilesNames)): case .conversationAction(.sendMediaMessages(let from, let to, let messageIds, let localFilesNames)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError)))
@ -393,10 +420,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .sharingAction(.retrySharing(let id)): case .sharingAction(.retrySharing(let id)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError)))
@ -416,10 +445,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.xmppSharingUploadSuccess(let messageId, let remotePath)): case .xmppAction(.xmppSharingUploadSuccess(let messageId, let remotePath)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: L10n.Global.Error.genericDbError)))
@ -439,10 +470,12 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.xmppSharingUploadFailed(let messageId, _)): case .xmppAction(.xmppSharingUploadFailed(let messageId, _)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in Task(priority: .background) { [weak self] in
guard let database = self?.database else { guard let database = self?.database else {
promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: L10n.Global.Error.genericDbError))) promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: L10n.Global.Error.genericDbError)))
@ -462,6 +495,7 @@ final class DatabaseMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
default: default:

View file

@ -10,7 +10,8 @@ final class FileMiddleware {
switch action { switch action {
// MARK: - For incomig attachments // MARK: - For incomig attachments
case .conversationAction(.messagesUpdated(let messages)): case .conversationAction(.messagesUpdated(let messages)):
return Future { [weak self] promise in return Deferred {
Future { [weak self] promise in
guard let wSelf = self else { guard let wSelf = self else {
promise(.success(.info("FileMiddleware: on checking attachments/shares messages, middleware self is nil"))) promise(.success(.info("FileMiddleware: on checking attachments/shares messages, middleware self is nil")))
return return
@ -47,10 +48,13 @@ final class FileMiddleware {
} }
promise(.success(.info("FileMiddleware: attachments/shares messages processed"))) promise(.success(.info("FileMiddleware: attachments/shares messages processed")))
}.eraseToAnyPublisher() }
}
.eraseToAnyPublisher()
case .fileAction(.downloadAttachmentFile(let id, let attachmentRemotePath)): case .fileAction(.downloadAttachmentFile(let id, let attachmentRemotePath)):
return Future { promise in return Deferred {
Future { promise in
let localName = "\(id)_\(UUID().uuidString)\(attachmentRemotePath.lastPathComponent)" let localName = "\(id)_\(UUID().uuidString)\(attachmentRemotePath.lastPathComponent)"
let localUrl = FileProcessing.fileFolder.appendingPathComponent(localName) let localUrl = FileProcessing.fileFolder.appendingPathComponent(localName)
DownloadManager.shared.enqueueDownload(from: attachmentRemotePath, to: localUrl) { error in DownloadManager.shared.enqueueDownload(from: attachmentRemotePath, to: localUrl) { error in
@ -63,17 +67,22 @@ final class FileMiddleware {
} }
} }
promise(.success(.info("FileMiddleware: started downloading attachment for message \(id)"))) promise(.success(.info("FileMiddleware: started downloading attachment for message \(id)")))
}.eraseToAnyPublisher() }
}
.eraseToAnyPublisher()
case .fileAction(.attachmentFileDownloaded(let id, let localName)): case .fileAction(.attachmentFileDownloaded(let id, let localName)):
return Future { [weak self] promise in return Deferred {
Future { [weak self] promise in
self?.downloadingMessageIDs.remove(id) self?.downloadingMessageIDs.remove(id)
promise(.success(.fileAction(.createAttachmentThumbnail(messageId: id, localName: localName)))) promise(.success(.fileAction(.createAttachmentThumbnail(messageId: id, localName: localName))))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.createAttachmentThumbnail(let id, let localName)): case .fileAction(.createAttachmentThumbnail(let id, let localName)):
return Future { [weak self] promise in return Deferred {
Future { [weak self] promise in
if let thumbnailName = FileProcessing.shared.createThumbnail(localName: localName) { if let thumbnailName = FileProcessing.shared.createThumbnail(localName: localName) {
self?.downloadingMessageIDs.remove(id) self?.downloadingMessageIDs.remove(id)
promise(.success(.fileAction(.attachmentThumbnailCreated(messageId: id, thumbnailName: thumbnailName)))) promise(.success(.fileAction(.attachmentThumbnailCreated(messageId: id, thumbnailName: thumbnailName))))
@ -82,38 +91,47 @@ final class FileMiddleware {
promise(.success(.info("FileMiddleware: failed to create thumbnail from \(localName) for message \(id)"))) promise(.success(.info("FileMiddleware: failed to create thumbnail from \(localName) for message \(id)")))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
// MARK: - For outgoing sharing // MARK: - For outgoing sharing
case .fileAction(.fetchItemsFromGallery): case .fileAction(.fetchItemsFromGallery):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
let items = FileProcessing.shared.fetchGallery() let items = FileProcessing.shared.fetchGallery()
promise(.success(.fileAction(.itemsFromGalleryFetched(items: items)))) promise(.success(.fileAction(.itemsFromGalleryFetched(items: items))))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.itemsFromGalleryFetched(let items)): case .fileAction(.itemsFromGalleryFetched(let items)):
return Future { promise in return Deferred {
Future { promise in
let newItems = FileProcessing.shared.fillGalleryItemsThumbnails(items: items) let newItems = FileProcessing.shared.fillGalleryItemsThumbnails(items: items)
promise(.success(.sharingAction(.galleryItemsUpdated(items: newItems)))) promise(.success(.sharingAction(.galleryItemsUpdated(items: newItems))))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.copyGalleryItemsForUploading(let items)): case .fileAction(.copyGalleryItemsForUploading(let items)):
return Future { promise in return Deferred {
Future { promise in
let ids = FileProcessing.shared.copyGalleryItemsForUploading(items: items) let ids = FileProcessing.shared.copyGalleryItemsForUploading(items: items)
promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: ids.map { $0.0 }, localNames: ids.map { $0.1 })))) promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: ids.map { $0.0 }, localNames: ids.map { $0.1 }))))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.copyCameraCapturedForUploading(let media, let type)): case .fileAction(.copyCameraCapturedForUploading(let media, let type)):
return Future { promise in return Deferred {
Future { promise in
if let (id, localName) = FileProcessing.shared.copyCameraCapturedForUploading(media: media, type: type) { if let (id, localName) = FileProcessing.shared.copyCameraCapturedForUploading(media: media, type: type) {
promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: [id], localNames: [localName])))) promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: [id], localNames: [localName]))))
} else { } else {
promise(.success(.info("FileMiddleware: failed to copy camera captured media for uploading"))) promise(.success(.info("FileMiddleware: failed to copy camera captured media for uploading")))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
default: default:

View file

@ -55,3 +55,15 @@ private var dateFormatter: DateFormatter {
formatter.dateFormat = "MM-dd HH:mm:ss.SSS" formatter.dateFormat = "MM-dd HH:mm:ss.SSS"
return formatter return formatter
} }
// For thread debugging
func ptInfo(_ message: String) {
#if DEBUG
let timeStr = dateFormatter.string(from: Date())
let str = "\(timeStr) \(message) -> \(Thread.current), \(String(validatingUTF8: __dispatch_queue_get_label(nil)) ?? "no queue label")"
print(str)
if isConsoleLoggingEnabled {
NSLog(str)
}
#endif
}

View file

@ -7,7 +7,8 @@ final class MessagesMiddleware {
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> { func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action { switch action {
case .conversationAction(.makeConversationActive(let chat, let roster)): case .conversationAction(.makeConversationActive(let chat, let roster)):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
if let currentClient = state.accountsState.accounts.first(where: { $0.bareJid == chat.account }) { if let currentClient = state.accountsState.accounts.first(where: { $0.bareJid == chat.account }) {
let features = state.accountsState.discoFeatures[currentClient.bareJid] ?? [] let features = state.accountsState.discoFeatures[currentClient.bareJid] ?? []
if features.map({ $0.xep }).contains("XEP-0313") { if features.map({ $0.xep }).contains("XEP-0313") {
@ -21,6 +22,7 @@ final class MessagesMiddleware {
promise(.success(.info("MessageMiddleware: No client found for account \(chat.account), probably some error here"))) promise(.success(.info("MessageMiddleware: No client found for account \(chat.account), probably some error here")))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
default: default:

View file

@ -11,7 +11,8 @@ final class SharingMiddleware {
switch action { switch action {
// MARK: - Camera and Gallery Access // MARK: - Camera and Gallery Access
case .sharingAction(.checkCameraAccess): case .sharingAction(.checkCameraAccess):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
let status = AVCaptureDevice.authorizationStatus(for: .video) let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status { switch status {
case .authorized: case .authorized:
@ -29,10 +30,12 @@ final class SharingMiddleware {
promise(.success(.sharingAction(.setCameraAccess(false)))) promise(.success(.sharingAction(.setCameraAccess(false))))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .sharingAction(.checkGalleryAccess): case .sharingAction(.checkGalleryAccess):
return Future<AppAction, Never> { promise in return Deferred {
Future<AppAction, Never> { promise in
let status = PHPhotoLibrary.authorizationStatus() let status = PHPhotoLibrary.authorizationStatus()
switch status { switch status {
case .authorized, .limited: case .authorized, .limited:
@ -50,6 +53,7 @@ final class SharingMiddleware {
promise(.success(.sharingAction(.setGalleryAccess(false)))) promise(.success(.sharingAction(.setGalleryAccess(false))))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.itemsFromGalleryFetched(let items)): case .fileAction(.itemsFromGalleryFetched(let items)):
@ -58,10 +62,12 @@ final class SharingMiddleware {
// MARK: - Sharing // MARK: - Sharing
case .sharingAction(.shareMedia(let ids)): case .sharingAction(.shareMedia(let ids)):
return Future { promise in return Deferred {
Future { promise in
let items = state.sharingState.galleryItems.filter { ids.contains($0.id) } let items = state.sharingState.galleryItems.filter { ids.contains($0.id) }
promise(.success(.fileAction(.copyGalleryItemsForUploading(items: items)))) promise(.success(.fileAction(.copyGalleryItemsForUploading(items: items))))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .fileAction(.itemsCopiedForUploading(let newMessageIds, let localNames)): case .fileAction(.itemsCopiedForUploading(let newMessageIds, let localNames)):
@ -78,7 +84,8 @@ final class SharingMiddleware {
} }
case .sharingAction(.cameraCaptured(let media, let type)): case .sharingAction(.cameraCaptured(let media, let type)):
return Future { promise in return Deferred {
Future { promise in
if let (id, localName) = FileProcessing.shared.copyCameraCapturedForUploading(media: media, type: type) { if let (id, localName) = FileProcessing.shared.copyCameraCapturedForUploading(media: media, type: type) {
promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: [id], localNames: [localName]))) promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: [id], localNames: [localName])))
) )
@ -86,6 +93,7 @@ final class SharingMiddleware {
promise(.success(.info("SharingMiddleware: camera's captured file didn't copied"))) promise(.success(.info("SharingMiddleware: camera's captured file didn't copied")))
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .sharingAction(.shareLocation(let lat, let lon)): case .sharingAction(.shareLocation(let lat, let lon)):
@ -98,11 +106,13 @@ final class SharingMiddleware {
} }
case .sharingAction(.shareDocuments(let data, let extensions)): case .sharingAction(.shareDocuments(let data, let extensions)):
return Future { promise in return Deferred {
Future { promise in
let ids = FileProcessing.shared.copyDocumentsForUploading(data: data, extensions: extensions) let ids = FileProcessing.shared.copyDocumentsForUploading(data: data, extensions: extensions)
promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: ids.map { $0.0 }, localNames: ids.map { $0.1 }))) promise(.success(.fileAction(.itemsCopiedForUploading(newMessageIds: ids.map { $0.0 }, localNames: ids.map { $0.1 })))
) )
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .sharingAction(.shareContact(let jid)): case .sharingAction(.shareContact(let jid)):

View file

@ -39,28 +39,35 @@ final class XMPPMiddleware {
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> { func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action { switch action {
case .accountsAction(.tryAddAccountWithCredentials): case .accountsAction(.tryAddAccountWithCredentials):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
self?.service.updateClients(for: state.accountsState.accounts) self?.service.updateClients(for: state.accountsState.accounts)
promise(.success(.info("XMPPMiddleware: clients updated in XMPP service"))) promise(.success(.info("XMPPMiddleware: clients updated in XMPP service")))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .accountsAction(.addAccountError): case .accountsAction(.addAccountError):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
self?.service.updateClients(for: state.accountsState.accounts) self?.service.updateClients(for: state.accountsState.accounts)
promise(.success(.info("XMPPMiddleware: clients updated in XMPP service"))) promise(.success(.info("XMPPMiddleware: clients updated in XMPP service")))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .databaseAction(.storedAccountsLoaded(let accounts)): case .databaseAction(.storedAccountsLoaded(let accounts)):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
self?.service.updateClients(for: accounts.filter { $0.isActive && !$0.isTemp }) self?.service.updateClients(for: accounts.filter { $0.isActive && !$0.isTemp })
promise(.success(.info("XMPPMiddleware: clients updated in XMPP service"))) promise(.success(.info("XMPPMiddleware: clients updated in XMPP service")))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .rostersAction(.addRoster(let ownerJID, let contactJID, let name, let groups)): case .rostersAction(.addRoster(let ownerJID, let contactJID, let name, let groups)):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
guard let service = self?.service, let client = service.clients.first(where: { $0.connectionConfiguration.userJid.stringValue == ownerJID }) else { guard let service = self?.service, let client = service.clients.first(where: { $0.connectionConfiguration.userJid.stringValue == ownerJID }) else {
return promise(.success(.rostersAction(.addRosterError(reason: XMPPError.item_not_found.localizedDescription)))) return promise(.success(.rostersAction(.addRosterError(reason: XMPPError.item_not_found.localizedDescription))))
} }
@ -75,10 +82,12 @@ final class XMPPMiddleware {
} }
}) })
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .rostersAction(.deleteRoster(let ownerJID, let contactJID)): case .rostersAction(.deleteRoster(let ownerJID, let contactJID)):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
guard let service = self?.service, let client = service.clients.first(where: { $0.connectionConfiguration.userJid.stringValue == ownerJID }) else { guard let service = self?.service, let client = service.clients.first(where: { $0.connectionConfiguration.userJid.stringValue == ownerJID }) else {
return promise(.success(.rostersAction(.rosterDeletingFailed(reason: XMPPError.item_not_found.localizedDescription)))) return promise(.success(.rostersAction(.rosterDeletingFailed(reason: XMPPError.item_not_found.localizedDescription))))
} }
@ -93,10 +102,12 @@ final class XMPPMiddleware {
} }
}) })
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.xmppMessageSent(let message)): case .xmppAction(.xmppMessageSent(let message)):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
DispatchQueue.global().async { DispatchQueue.global().async {
self?.service.sendMessage(message: message) { done in self?.service.sendMessage(message: message) { done in
if done { if done {
@ -107,10 +118,12 @@ final class XMPPMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.xmppSharingTryUpload(let message)): case .xmppAction(.xmppSharingTryUpload(let message)):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
if self?.uploadingMessageIDs.contains(message.id) ?? false { if self?.uploadingMessageIDs.contains(message.id) ?? false {
return promise(.success(.info("XMPPMiddleware: attachment in message \(message.id) is already in uploading process"))) return promise(.success(.info("XMPPMiddleware: attachment in message \(message.id) is already in uploading process")))
} else { } else {
@ -127,13 +140,16 @@ final class XMPPMiddleware {
} }
} }
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .xmppAction(.xmppLoadArchivedMessages(let jid, let to, let fromDate)): case .xmppAction(.xmppLoadArchivedMessages(let jid, let to, let fromDate)):
return Future<AppAction, Never> { [weak self] promise in return Deferred {
Future<AppAction, Never> { [weak self] promise in
self?.service.requestArchivedMessages(jid: jid, to: to, fromDate: fromDate) self?.service.requestArchivedMessages(jid: jid, to: to, fromDate: fromDate)
promise(.success(.info("XMPPMiddleware: archived messages requested for \(jid) from \(fromDate)"))) promise(.success(.info("XMPPMiddleware: archived messages requested for \(jid) from \(fromDate)")))
} }
}
.eraseToAnyPublisher() .eraseToAnyPublisher()
default: default: