import Combine import Foundation import GRDB final class DatabaseMiddleware { static let shared = DatabaseMiddleware() private let database = Database.shared private var cancellables: Set = [] private init() { // Database changes ValueObservation .tracking(Roster.fetchAll) .publisher(in: database._db, scheduling: .immediate) .sink { _ in // Handle completion } receiveValue: { rosters in DispatchQueue.main.async { store.dispatch(.databaseAction(.storedRostersLoaded(rosters: rosters))) } } .store(in: &cancellables) ValueObservation .tracking(Chat.fetchAll) .publisher(in: database._db, scheduling: .immediate) .sink { _ in // Handle completion } receiveValue: { chats in DispatchQueue.main.async { store.dispatch(.databaseAction(.storedChatsLoaded(chats: chats))) } } .store(in: &cancellables) } func middleware(state _: AppState, action: AppAction) -> AnyPublisher { switch action { case .startAction(.loadStoredAccounts): return Future { promise in Task(priority: .background) { [weak self] in guard let database = self?.database else { promise(.success(.databaseAction(.loadingStoredAccountsFailed))) return } do { try database._db.read { db in let accounts = try Account.fetchAll(db) promise(.success(.databaseAction(.storedAccountsLoaded(accounts: accounts)))) } } catch { promise(.success(.databaseAction(.loadingStoredAccountsFailed))) } } } .eraseToAnyPublisher() case .accountsAction(.makeAccountPermanent(let account)): return Future { promise in Task(priority: .background) { [weak self] in guard let database = self?.database else { promise(.success(.databaseAction(.updateAccountFailed))) return } do { try database._db.write { db in // make permanent and store to database var acc = account acc.isTemp = false try acc.insert(db) // Re-Fetch all accounts let accounts = try Account.fetchAll(db) // Use the accounts promise(.success(.databaseAction(.storedAccountsLoaded(accounts: accounts)))) } } catch { promise(.success(.databaseAction(.updateAccountFailed))) } } } .eraseToAnyPublisher() case .rostersAction(.markRosterAsLocallyDeleted(let ownerJID, let contactJID)): return Future { promise in Task(priority: .background) { [weak self] in guard let database = self?.database else { promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError)))) return } do { _ = try database._db.write { db in try Roster .filter(Column("bareJid") == ownerJID) .filter(Column("contactBareJid") == contactJID) .updateAll(db, Column("locallyDeleted").set(to: true)) } promise(.success(.empty)) } catch { promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError)))) } } } .eraseToAnyPublisher() case .rostersAction(.unmarkRosterAsLocallyDeleted(let ownerJID, let contactJID)): return Future { promise in Task(priority: .background) { [weak self] in guard let database = self?.database else { promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError)))) return } do { _ = try database._db.write { db in try Roster .filter(Column("bareJid") == ownerJID) .filter(Column("contactBareJid") == contactJID) .updateAll(db, Column("locallyDeleted").set(to: false)) } promise(.success(.empty)) } catch { promise(.success(.rostersAction(.rosterDeletingFailed(reason: L10n.Global.Error.genericDbError)))) } } } .eraseToAnyPublisher() case .chatsAction(.createNewChat(let accountJid, let participantJid)): return Future { promise in Task(priority: .background) { [weak self] in guard let database = self?.database else { promise(.success(.chatsAction(.chatCreationFailed(reason: L10n.Global.Error.genericDbError)))) return } do { try database._db.write { db in let chat = Chat( id: UUID().uuidString, account: accountJid, participant: participantJid, type: .chat ) try chat.insert(db) promise(.success(.chatsAction(.chatCreated(chat: chat)))) } } catch { promise(.success(.chatsAction(.chatCreationFailed(reason: L10n.Global.Error.genericDbError)))) } } } .eraseToAnyPublisher() case .xmppAction(.xmppMessageReceived(let message)): if message.type != .chat { return Empty().eraseToAnyPublisher() } // TODO: Store msg here! return Empty().eraseToAnyPublisher() default: return Empty().eraseToAnyPublisher() } } }