diff --git a/Shared/database/DBSchemaManager.swift b/Shared/database/DBSchemaManager.swift index ad12dbe..78bb695 100644 --- a/Shared/database/DBSchemaManager.swift +++ b/Shared/database/DBSchemaManager.swift @@ -24,7 +24,7 @@ import TigaseSwift public class DBSchemaManager { - static let CURRENT_VERSION = 14; + static let CURRENT_VERSION = 15; fileprivate let dbConnection: DBConnection; @@ -69,32 +69,6 @@ public class DBSchemaManager { try dbConnection.execute("ALTER TABLE chat_history ADD COLUMN error TEXT;"); } -// let queryStmt = try dbConnection.prepareStatement("SELECT account, jid, encryption FROM chats WHERE encryption IS NOT NULL AND options IS NULL"); -// let toConvert = try queryStmt.query { (cursor) -> (BareJID, BareJID, ChatEncryption)? in -// let account: BareJID = cursor["account"]!; -// let jid: BareJID = cursor["jid"]!; -// guard let encryptionStr: String = cursor["encryption"] else { -// return nil; -// } -// guard let encryption = ChatEncryption(rawValue: encryptionStr) else { -// return nil; -// } -// -// return (account, jid, encryption); -// } -// if !toConvert.isEmpty { -// let updateStmt = try dbConnection.prepareStatement("UPDATE chats SET options = ?, encryption = null WHERE account = ? AND jid = ?"); -// try toConvert.forEach { (arg0) in -// let (account, jid, encryption) = arg0 -// var options = ChatOptions(); -// options.encryption = encryption; -// let data = try? JSONEncoder().encode(options); -// let dataStr = data != nil ? String(data: data!, encoding: .utf8)! : nil; -// _ = try updateStmt.update(dataStr, account, jid); -// } -// } - - let toRemove: [(String,String,Int32)] = try dbConnection.prepareStatement("SELECT sess.account as account, sess.name as name, sess.device_id as deviceId FROM omemo_sessions sess WHERE NOT EXISTS (select 1 FROM omemo_identities i WHERE i.account = sess.account and i.name = sess.name and i.device_id = sess.device_id)").query([:] as [String: Any?], map: { (cursor:DBCursor) -> (String, String, Int32)? in return (cursor["account"]!, cursor["name"]!, cursor["deviceId"]!); }); diff --git a/Shared/db-schema-15.sql b/Shared/db-schema-15.sql new file mode 100644 index 0000000..05096f6 --- /dev/null +++ b/Shared/db-schema-15.sql @@ -0,0 +1,13 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS last_message_sync ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + account TEXT NOT NULL COLLATE NOCASE, + jid TEXT NOT NULL COLLATE NOCASE, + received_id TEXT, + read_id TEXT +); + +COMMIT; + +PRAGMA user_version = 15; diff --git a/Snikket.xcodeproj/project.pbxproj b/Snikket.xcodeproj/project.pbxproj index ed17518..215dcf3 100644 --- a/Snikket.xcodeproj/project.pbxproj +++ b/Snikket.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 379D914A26E8A0E300B877CA /* db-schema-15.sql in Resources */ = {isa = PBXBuildFile; fileRef = 379D914926E8A0E300B877CA /* db-schema-15.sql */; }; + 379D914C26E8A29800B877CA /* DBLastMessageSyncStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379D914B26E8A29800B877CA /* DBLastMessageSyncStore.swift */; }; E928AD4326D6A08A00F29F93 /* db-schema-14.sql in Resources */ = {isa = PBXBuildFile; fileRef = E928AD4226D6A08A00F29F93 /* db-schema-14.sql */; }; E95AA70226D38B6F00A38D44 /* DisplayNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95AA70126D38B6E00A38D44 /* DisplayNameViewController.swift */; }; E963721026D786D000332482 /* BlockingCommandModuleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E963720F26D786D000332482 /* BlockingCommandModuleExtension.swift */; }; @@ -295,6 +297,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 379D914926E8A0E300B877CA /* db-schema-15.sql */ = {isa = PBXFileReference; lastKnownFileType = text; path = "db-schema-15.sql"; sourceTree = ""; }; + 379D914B26E8A29800B877CA /* DBLastMessageSyncStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBLastMessageSyncStore.swift; sourceTree = ""; }; E928AD4226D6A08A00F29F93 /* db-schema-14.sql */ = {isa = PBXFileReference; lastKnownFileType = text; path = "db-schema-14.sql"; sourceTree = ""; }; E95AA70126D38B6E00A38D44 /* DisplayNameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameViewController.swift; sourceTree = ""; }; E963720F26D786D000332482 /* BlockingCommandModuleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingCommandModuleExtension.swift; sourceTree = ""; }; @@ -659,6 +663,7 @@ FEE49DCF2426485500900BBB /* DBConnection_main.swift */, FE3E387B242766A900D3A8E8 /* DBChannelStore.swift */, FEBC12F324C70E2900689475 /* DBChatHistorySyncStore.swift */, + 379D914B26E8A29800B877CA /* DBLastMessageSyncStore.swift */, ); path = database; sourceTree = ""; @@ -805,6 +810,7 @@ FE3BA0BF24B61583000C80D4 /* db-schema-12.sql */, FEBC12F124C70DE000689475 /* db-schema-13.sql */, E928AD4226D6A08A00F29F93 /* db-schema-14.sql */, + 379D914926E8A0E300B877CA /* db-schema-15.sql */, ); path = Shared; sourceTree = ""; @@ -1183,6 +1189,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 379D914A26E8A0E300B877CA /* db-schema-15.sql in Resources */, FE10BCF323FD4EF000E214F3 /* db-schema-10.sql in Resources */, FE759FF22371F21C001E78D9 /* db-schema-6.sql in Resources */, FE759FEF2371F21C001E78D9 /* db-schema-3.sql in Resources */, @@ -1415,6 +1422,7 @@ FE0E30F12535BB530030F8C5 /* BaseChatViewController+ShareFile.swift in Sources */, FE507A251CDB7B3B001A015C /* AccountManager.swift in Sources */, FEC79195241ABEF4007BE572 /* MessageState.swift in Sources */, + 379D914C26E8A29800B877CA /* DBLastMessageSyncStore.swift in Sources */, FE719E7C22730DC3007CEEC9 /* OMEMOIdentityTableViewCell.swift in Sources */, FEDE93901D09BB8300CA60A9 /* VCardEditPhoneTableViewCell.swift in Sources */, FE507A221CDB7B3B001A015C /* AvatarStatusView.swift in Sources */, diff --git a/Snikket/database/DBChatHistoryStore.swift b/Snikket/database/DBChatHistoryStore.swift index 3d99608..1bada73 100644 --- a/Snikket/database/DBChatHistoryStore.swift +++ b/Snikket/database/DBChatHistoryStore.swift @@ -450,7 +450,6 @@ public class DBChatHistoryStore { let uuid = UUID(); previewsInProgress[masterId] = uuid; previewGenerationDispatcher.async { - print("generating previews for master id:", masterId, "uuid:", uuid); // if we may have previews, we should add them here.. if let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) { let matches = detector.matches(in: data, range: NSMakeRange(0, data.utf16.count)); @@ -464,7 +463,6 @@ public class DBChatHistoryStore { }) else { return; } - print("adding previews for master id:", masterId, "uuid:", uuid); matches.forEach { match in if let url = match.url, let scheme = url.scheme, ["https", "http"].contains(scheme) { if (data as NSString).range(of: "http", options: .caseInsensitive, range: match.range).location == match.range.location { diff --git a/Snikket/database/DBLastMessageSyncStore.swift b/Snikket/database/DBLastMessageSyncStore.swift new file mode 100644 index 0000000..b5dce52 --- /dev/null +++ b/Snikket/database/DBLastMessageSyncStore.swift @@ -0,0 +1,79 @@ +// +// DBLastMessageSyncStore.swift +// Snikket +// +// Created by Hammad Ashraf on 08/09/2021. +// Copyright © 2021 Snikket. All rights reserved. +// + +import Foundation +import TigaseSwift +import Shared + +// Table last_message_sync :- +// account TEXT +// jid TEXT +// received_id TEXT +// read_id TEXT + +class DBLastMessageSyncStore { + + static let instance = DBLastMessageSyncStore() + + fileprivate let dispatcher: QueueDispatcher; + + private let insertLastMessage: DBStatement + private let getLastMessage: DBStatement + private let updateLastMessage: DBStatement + + private init() { + insertLastMessage = try! DBConnection.main.prepareStatement("INSERT INTO last_message_sync (account, jid, received_id, read_id) VALUES (:account, :jid, :received_id, :read_id)") + + updateLastMessage = try! DBConnection.main.prepareStatement("UPDATE last_message_sync SET received_id = :received_id, read_id = :read_id WHERE account = :account AND jid = :jid") + + getLastMessage = try! DBConnection.main.prepareStatement("SELECT account, jid, received_id, read_id FROM last_message_sync WHERE account = :account AND jid = :jid") + + dispatcher = QueueDispatcher(label: "last_message_sync") + } + + func insertmessage(account: BareJID, jid: BareJID, receivedId: String?, readId: String?) { + let params: [String:Any?] = ["account":account, "jid":jid, "received_id": receivedId, "read_id":readId] + dispatcher.async { + _ = try! self.insertLastMessage.insert(params) + } + } + + func updateMessage(account: BareJID, jid: BareJID, receivedId: String?, readId: String?) { + let params: [String:Any?] = ["account":account, "jid":jid, "received_id": receivedId, "read_id":readId] + dispatcher.async { + _ = try! self.updateLastMessage.update(params) + } + } + + func getLastMessage(account: BareJID, jid: BareJID) -> LastMessageSync? { + let params: [String:Any?] = ["account":account, "jid":jid] + let message = try! self.getLastMessage.queryFirstMatching(params) { (cursor) -> LastMessageSync? in + return LastMessageSync(account: account, jid: jid, receivedId: cursor["received_id"], readId: cursor["read_id"]) + } + return message + } + + func updateOrInsertLastMessage(account: BareJID, jid: BareJID, receivedId: String?, readId: String?) { + dispatcher.async { + let lastMessage = self.getLastMessage(account: account, jid: jid) + if lastMessage != nil, receivedId != nil { + self.updateMessage(account: account, jid: jid, receivedId: receivedId, readId: readId) + } + else if lastMessage == nil { + self.insertmessage(account: account, jid: jid, receivedId: receivedId, readId: readId) + } + } + } +} + +struct LastMessageSync { + var account : BareJID + var jid : BareJID + var receivedId : String? + var readId : String? +} diff --git a/Snikket/service/MessageEventHandler.swift b/Snikket/service/MessageEventHandler.swift index df1adfd..0e26bd9 100644 --- a/Snikket/service/MessageEventHandler.swift +++ b/Snikket/service/MessageEventHandler.swift @@ -362,6 +362,30 @@ class MessageEventHandler: XmppServiceEventHandler { syncMessages(for: account, since: syncMessagesSince); } } + + static func syncMessagesAfterID(for account: BareJID, version: MessageArchiveManagementModule.Version?, componentJID: JID, afterId: String) { + guard let client = XmppService.instance.getClient(for: account), let mamModule: MessageArchiveManagementModule = client.modulesManager.getModule(MessageArchiveManagementModule.ID) else { return } + + let queryId = UUID().uuidString + + let query = JabberDataElement(type: .submit) + let formTypeField = HiddenField(name: "FORM_TYPE") + formTypeField.value = version?.rawValue + query.addField(formTypeField) + + let withField = JidSingleField(name: "with") + withField.value = componentJID + query.addField(withField) + + let startField = TextSingleField(name: "after-id") + startField.value = afterId + query.addField(startField) + + mamModule.queryItems(version: version, componentJid: componentJID, node: nil, query: query, queryId: queryId, rsm: RSM.Query(after:afterId ,max: 150)) { responseStanza in + return + } + + } static func syncMessages(for account: BareJID, version: MessageArchiveManagementModule.Version? = nil, componentJID: JID? = nil, since: Date, rsmQuery: RSM.Query? = nil) { let period = DBChatHistorySyncStore.Period(account: account, component: componentJID?.bareJid, from: since, after: nil); diff --git a/Snikket/service/MucEventHandler.swift b/Snikket/service/MucEventHandler.swift index 86e0e4a..76de506 100644 --- a/Snikket/service/MucEventHandler.swift +++ b/Snikket/service/MucEventHandler.swift @@ -54,13 +54,21 @@ class MucEventHandler: XmppServiceEventHandler { room.supportedFeatures = []; break; } + let account = e.sessionObject.userBareJid!; if let timestamp = room.lastMessageDate, !mamVersions.isEmpty { - let account = e.sessionObject.userBareJid!; room.onRoomJoined = { room in MessageEventHandler.syncMessages(for: account, version: mamVersions.contains(.MAM2) ? .MAM2 : .MAM1, componentJID: room.jid, since: timestamp); } _ = room.rejoin(skipHistoryFetch: true); NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room); + } else if let lastMessageRecieved = DBLastMessageSyncStore.instance.getLastMessage(account: account, jid: room.roomJid), let afterId = lastMessageRecieved.receivedId { + + room.onRoomJoined = { room in + MessageEventHandler.syncMessagesAfterID(for: account, version: mamVersions.contains(.MAM2) ? .MAM2 : .MAM1, componentJID: room.jid, afterId: afterId) + } + room.lastMessageDate = Date() + _ = room.rejoin(skipHistoryFetch: true); + NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room); } else { _ = room.rejoin(); NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room); @@ -101,7 +109,9 @@ class MucEventHandler: XmppServiceEventHandler { } } - DBChatHistoryStore.instance.append(for: room.account, message: e.message, source: .stream); + DBChatHistoryStore.instance.append(for: room.account, message: e.message, source: .stream) + + DBLastMessageSyncStore.instance.updateOrInsertLastMessage(account: room.account, jid: room.roomJid, receivedId: e.message.id, readId: nil) case let e as MucModule.AbstractOccupantEvent: if let room = e.room as? DBRoom, room.isOMEMOCapable, let jid = e.occupant.jid { if (e.occupant.affiliation == .none || e.occupant.affiliation == .outcast) {