diff --git a/ConversationsClassic/AppData/Client/Client.swift b/ConversationsClassic/AppData/Client/Client.swift index c3a656a..aa28f02 100644 --- a/ConversationsClassic/AppData/Client/Client.swift +++ b/ConversationsClassic/AppData/Client/Client.swift @@ -41,7 +41,9 @@ final class Client: ObservableObject { } } } +} +extension Client { func addRoster(_ jid: String, name: String?, groups: [String]) async throws { _ = try await connection.module(.roster).addItem( jid: JID(jid), @@ -53,6 +55,17 @@ final class Client: ObservableObject { func deleteRoster(_ roster: Roster) async throws { _ = try await connection.module(.roster).removeItem(jid: JID(roster.contactBareJid)) } + + func connect() async { + guard credentials.isActive, state == .enabled(.disconnected) else { + return + } + try? await connection.loginAndWait() + } + + func disconnect() { + _ = connection.disconnect() + } } extension Client { diff --git a/ConversationsClassic/AppData/Stores/ClientsStore.swift b/ConversationsClassic/AppData/Stores/ClientsStore.swift index 7925984..e16a294 100644 --- a/ConversationsClassic/AppData/Stores/ClientsStore.swift +++ b/ConversationsClassic/AppData/Stores/ClientsStore.swift @@ -9,9 +9,10 @@ final class ClientsStore: ObservableObject { @Published private(set) var ready = false @Published private(set) var clients: [Client] = [] + private let observation = ValueObservation.tracking(Credentials.fetchAll) + func startFetching() { Task { - let observation = ValueObservation.tracking(Credentials.fetchAll) do { for try await credentials in observation.values(in: Database.shared.dbQueue) { processCredentials(credentials) @@ -22,6 +23,23 @@ final class ClientsStore: ObservableObject { } } + private func processCredentials(_ credentials: [Credentials]) { + let existsJids = Set(clients.map { $0.credentials.bareJid }) + let credentialsJids = Set(credentials.map { $0.bareJid }) + + let forAdd = credentials.filter { !existsJids.contains($0.bareJid) } + let newClients = forAdd.map { Client(credentials: $0) } + + let forRemove = clients.filter { !credentialsJids.contains($0.credentials.bareJid) } + forRemove.forEach { $0.disconnect() } + + var updatedClients = clients.filter { credentialsJids.contains($0.credentials.bareJid) } + updatedClients.append(contentsOf: newClients) + clients = updatedClients + } +} + +extension ClientsStore { func addNewClient(_ client: Client) { clients.append(client) Task(priority: .background) { @@ -29,13 +47,15 @@ final class ClientsStore: ObservableObject { } } - private func processCredentials(_ credentials: [Credentials]) { - let existsJids = clients.map { $0.credentials.bareJid } - let forAdd = credentials.filter { !existsJids.contains($0.bareJid) } - let forRemove = existsJids.filter { !credentials.map { $0.bareJid }.contains($0) } - - var newClients = clients.filter { !forRemove.contains($0.credentials.bareJid) } - newClients.append(contentsOf: forAdd.map { Client(credentials: $0) }) - clients = newClients + func reconnectAll() { + Task { + await withTaskGroup(of: Void.self) { taskGroup in + for client in clients { + taskGroup.addTask { + await client.connect() + } + } + } + } } } diff --git a/ConversationsClassic/AppData/Stores/RostersStore.swift b/ConversationsClassic/AppData/Stores/RostersStore.swift index 192ba20..b709903 100644 --- a/ConversationsClassic/AppData/Stores/RostersStore.swift +++ b/ConversationsClassic/AppData/Stores/RostersStore.swift @@ -9,17 +9,13 @@ final class RostersStore: ObservableObject { private var cancellable: AnyCancellable? - init(clientsPublisher: Published<[Client]>.Publisher) { - subscribeToClientsStore(clientsPublisher: clientsPublisher) - } - - private func subscribeToClientsStore(clientsPublisher: Published<[Client]>.Publisher) { + init() { let rostersPublisher = ValueObservation.tracking(Roster.fetchAll) .publisher(in: Database.shared.dbQueue) .receive(on: DispatchQueue.main) .catch { _ in Just([]) } - cancellable = clientsPublisher + cancellable = ClientsStore.shared.$clients .map { $0.filter { $0.state != .disabled } } // look rosters only for enabled clients .flatMap { clients in Publishers.MergeMany(clients.map { $0.$state }) @@ -46,18 +42,29 @@ extension RostersStore { case serverError } - func addRoster(ownerJid: String, contactJID: String, name _: String?, groups _: [String]) async -> RosterAddResult { + func addRoster(_ owner: Credentials, contactJID: String, name _: String?, groups _: [String]) async -> RosterAddResult { // check if such roster was already exists or locally deleted - if var exists = rosters.first(where: { $0.bareJid == ownerJid && $0.contactBareJid == contactJID }), exists.locallyDeleted { + if var exists = rosters.first(where: { $0.bareJid == owner.bareJid && $0.contactBareJid == contactJID }), exists.locallyDeleted { try? await exists.setLocallyDeleted(false) return .success } // add new roster // check that client is enabled and connected - guard let client = ClientsStore.shared.clients.first(where: { $0.credentials.bareJid == ownerJid }), client.state == .enabled(.connected) else { + let clientStorage = ClientsStore.shared + while !clientStorage.ready { + await Task.yield() + } + print("!! Clients: \(clientStorage.clients.count)") + guard let client = clientStorage.clients.first(where: { $0.credentials == owner }) else { return .connectionError } + guard client.state == .enabled(.connected) else { + return .connectionError + } + // guard let client = ClientsStore.shared.clients.first(where: { $0.credentials == owner }), client.state == .enabled(.connected) else { + // return .connectionError + // } // add roster do { diff --git a/ConversationsClassic/View/Main/Contacts/AddContactOrChannelScreen.swift b/ConversationsClassic/View/Main/Contacts/AddContactOrChannelScreen.swift index d3e826d..4c48c9e 100644 --- a/ConversationsClassic/View/Main/Contacts/AddContactOrChannelScreen.swift +++ b/ConversationsClassic/View/Main/Contacts/AddContactOrChannelScreen.swift @@ -130,7 +130,7 @@ struct AddContactOrChannelScreen: View { router.dismissModal() } - let result = await rostersStore.addRoster(ownerJid: ownerCredentials.bareJid, contactJID: contactJID, name: nil, groups: []) + let result = await rostersStore.addRoster(ownerCredentials, contactJID: contactJID, name: nil, groups: []) switch result { case .success: diff --git a/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift b/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift index a4c19c0..c7d673f 100644 --- a/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift +++ b/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift @@ -3,7 +3,7 @@ import SwiftUI struct ContactsScreen: View { @Environment(\.router) var router @EnvironmentObject var clientsStore: ClientsStore - @StateObject var rostersStore = RostersStore(clientsPublisher: ClientsStore.shared.$clients) + @StateObject var rostersStore = RostersStore() var body: some View { ZStack {