From 543f729247507f20e89d304235a26b7c7ebb160a Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 25 Aug 2022 17:24:43 +1000 Subject: [PATCH] feat: add local cache and deal with merging for reaction changes --- Session.xcodeproj/project.pbxproj | 4 ++ .../ConversationVC+Interaction.swift | 30 +++++++++ SessionMessagingKit/Messages/Message.swift | 38 +++++++++--- .../Open Groups/Models/PendingChange.swift | 41 +++++++++++++ .../Open Groups/OpenGroupAPI.swift | 18 +----- .../Open Groups/OpenGroupManager.swift | 61 ++++++++++++++++++- 6 files changed, 167 insertions(+), 25 deletions(-) create mode 100644 SessionMessagingKit/Open Groups/Models/PendingChange.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 15c1cea40..81e1ae0a9 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -139,6 +139,7 @@ 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; }; 7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */; }; 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682928B6F1420069F315 /* ReactionResponse.swift */; }; + 7B81682C28B72F480069F315 /* PendingChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682B28B72F480069F315 /* PendingChange.swift */; }; 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; }; 7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */; }; 7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */; }; @@ -1180,6 +1181,7 @@ 7B81682228A4C1210069F315 /* UpdateTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTypes.swift; sourceTree = ""; }; 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; 7B81682928B6F1420069F315 /* ReactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionResponse.swift; sourceTree = ""; }; + 7B81682B28B72F480069F315 /* PendingChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingChange.swift; sourceTree = ""; }; 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = ""; }; 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = ""; }; 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; @@ -3856,6 +3858,7 @@ FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */, FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */, 7B81682928B6F1420069F315 /* ReactionResponse.swift */, + 7B81682B28B72F480069F315 /* PendingChange.swift */, ); path = Models; sourceTree = ""; @@ -5193,6 +5196,7 @@ C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */, FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */, FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */, + 7B81682C28B72F480069F315 /* PendingChange.swift in Sources */, FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */, FD5C7309285007920029977D /* BlindedIdLookup.swift in Sources */, 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index ff2ecc991..394810b2b 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1137,6 +1137,14 @@ extension ConversationVC: else { return } if remove { + let pendingChange = OpenGroupManager + .addPendingReaction( + emoji: emoji, + id: openGroupServerMessageId, + in: openGroup.roomToken, + on: openGroup.server, + type: .remove + ) OpenGroupAPI .reactionDelete( db, @@ -1145,8 +1153,23 @@ extension ConversationVC: in: openGroup.roomToken, on: openGroup.server ) + .map { _, response in + OpenGroupManager + .updatePendingChange( + pendingChange, + seqNo: response.seqNo + ) + } .retainUntilComplete() } else { + let pendingChange = OpenGroupManager + .addPendingReaction( + emoji: emoji, + id: openGroupServerMessageId, + in: openGroup.roomToken, + on: openGroup.server, + type: .react + ) OpenGroupAPI .reactionAdd( db, @@ -1155,6 +1178,13 @@ extension ConversationVC: in: openGroup.roomToken, on: openGroup.server ) + .map { _, response in + OpenGroupManager + .updatePendingChange( + pendingChange, + seqNo: response.seqNo + ) + } .retainUntilComplete() } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index f37f93f10..b16291a26 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -353,6 +353,7 @@ public extension Message { _ db: Database, openGroupId: String, message: OpenGroupAPI.Message, + associatedPendingChanges: [OpenGroupAPI.PendingChange], dependencies: SMKDependencies = SMKDependencies() ) -> [Reaction] { var results: [Reaction] = [] @@ -364,14 +365,33 @@ public extension Message { threadVariant: .openGroup ) for (encodedEmoji, rawReaction) in reactions { - if let emoji = encodedEmoji.removingPercentEncoding, + if let decodedEmoji = encodedEmoji.removingPercentEncoding, rawReaction.count > 0, let reactors = rawReaction.reactors { + // Decide whether we need to add an extra reaction from current user + let pendingChanges = associatedPendingChanges + .filter { + if case .reaction(_, let emoji, _) = $0.metadata { + return emoji == decodedEmoji + } + return false + } + var shouldAddSelfReaction: Bool = rawReaction.you || reactors.contains(userPublicKey) + pendingChanges.forEach { + if case .reaction(_, _, let action) = $0.metadata { + switch action { + case .react: shouldAddSelfReaction = true + case .remove: shouldAddSelfReaction = false + } + } + } + + let count: Int64 = shouldAddSelfReaction ? rawReaction.count - 1 : rawReaction.count let timestampMs: Int64 = Int64(floor((Date().timeIntervalSince1970 * 1000))) - let maxLength: Int = !rawReaction.you || reactors.contains(userPublicKey) ? 5 : 4 + let maxLength: Int = shouldAddSelfReaction ? 4 : 5 let desiredReactorIds: [String] = reactors - .filter { $0 != blindedUserPublicKey } + .filter { $0 != blindedUserPublicKey && $0 != userPublicKey } // Remove current user for now, will add back if needed .prefix(maxLength) .map{ $0 } @@ -384,8 +404,8 @@ public extension Message { serverHash: nil, timestampMs: timestampMs, authorId: reactor, - emoji: emoji, - count: rawReaction.count, + emoji: decodedEmoji, + count: count, sortId: rawReaction.index ) } @@ -401,22 +421,22 @@ public extension Message { serverHash: nil, timestampMs: timestampMs, authorId: reactor, - emoji: emoji, + emoji: decodedEmoji, count: 0, // Only want this on the first reaction sortId: rawReaction.index ) } ) .appending( // Add the current user reaction (if applicable and not already included) - !rawReaction.you || reactors.contains(userPublicKey) ? + !shouldAddSelfReaction ? nil : Reaction( interactionId: message.id, serverHash: nil, timestampMs: timestampMs, authorId: userPublicKey, - emoji: emoji, - count: (desiredReactorIds.isEmpty ? rawReaction.count : 0), + emoji: decodedEmoji, + count: 1, sortId: rawReaction.index ) ) diff --git a/SessionMessagingKit/Open Groups/Models/PendingChange.swift b/SessionMessagingKit/Open Groups/Models/PendingChange.swift new file mode 100644 index 000000000..1c9c3e513 --- /dev/null +++ b/SessionMessagingKit/Open Groups/Models/PendingChange.swift @@ -0,0 +1,41 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension OpenGroupAPI { + public struct PendingChange: Equatable { + enum ChangeType { + case reaction + } + + enum Metadata { + case reaction(messageId: Int64, emoji: String, action: VisibleMessage.VMReaction.Kind) + } + + let server: String + let room: String + let changeType: ChangeType + var seqNo: Int64? + let metadata: Metadata + + public static func == (lhs: OpenGroupAPI.PendingChange, rhs: OpenGroupAPI.PendingChange) -> Bool { + guard lhs.server == rhs.server && + lhs.room == rhs.room && + lhs.changeType == rhs.changeType && + lhs.seqNo == rhs.seqNo + else { + return false + } + + switch lhs.changeType { + case .reaction: + if case .reaction(let lhsMessageId, let lhsEmoji, let lhsAction) = lhs.metadata, + case .reaction(let rhsMessageId, let rhsEmoji, let rhsAction) = rhs.metadata { + return lhsMessageId == rhsMessageId && lhsEmoji == rhsEmoji && lhsAction == rhsAction + } else { + return false + } + } + } + } +} diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 01ceb20fc..04cf63b57 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -701,7 +701,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { + ) -> Promise<(OnionRequestResponseInfoType, ReactionAddResponse)> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { @@ -719,10 +719,6 @@ public enum OpenGroupAPI { using: dependencies ) .decoded(as: ReactionAddResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) - .map { responseInfo, addResponse in - print("\(addResponse)") - return responseInfo - } } public static func reactionDelete( @@ -732,7 +728,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { + ) -> Promise<(OnionRequestResponseInfoType, ReactionRemoveResponse)> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { @@ -750,10 +746,6 @@ public enum OpenGroupAPI { using: dependencies ) .decoded(as: ReactionRemoveResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) - .map { responseInfo, removeResponse in - print("\(removeResponse)") - return responseInfo - } } public static func reactionDeleteAll( @@ -763,7 +755,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { + ) -> Promise<(OnionRequestResponseInfoType, ReactionRemoveAllResponse)> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { @@ -781,10 +773,6 @@ public enum OpenGroupAPI { using: dependencies ) .decoded(as: ReactionRemoveAllResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) - .map { responseInfo, removeAllResponse in - print("\(removeAllResponse)") - return responseInfo - } } // MARK: - Pinning diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 33fee96cc..4364a4dff 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -20,6 +20,8 @@ public protocol OGMCacheType { var timeSinceLastPoll: [String: TimeInterval] { get set } func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval + + var pendingChanges: [OpenGroupAPI.PendingChange] { get set } } // MARK: - OpenGroupManager @@ -53,6 +55,8 @@ public final class OpenGroupManager: NSObject { _timeSinceLastOpen = dependencies.date.timeIntervalSince(lastOpen) return dependencies.date.timeIntervalSince(lastOpen) } + + public var pendingChanges: [OpenGroupAPI.PendingChange] = [] } // MARK: - Variables @@ -529,11 +533,17 @@ public final class OpenGroupManager: NSObject { .filter { $0.deleted == true } .map { $0.id } - // Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId') if let seqNo: Int64 = seqNo { + // Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId') _ = try? OpenGroup .filter(id: openGroup.id) .updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: seqNo)) + + // Update pendingChange cache + dependencies.mutableCache.mutate { + $0.pendingChanges = $0.pendingChanges + .filter { $0.seqNo == nil || $0.seqNo! > seqNo } + } } // Process the messages @@ -589,6 +599,17 @@ public final class OpenGroupManager: NSObject { db, openGroupId: openGroup.id, message: message, + associatedPendingChanges: dependencies.cache.pendingChanges + .filter { + guard $0.server == server && $0.room == roomToken && $0.changeType == .reaction else { + return false + } + + if case .reaction(let messageId, _, _) = $0.metadata { + return messageId == message.id + } + return false + }, dependencies: dependencies ) @@ -738,6 +759,44 @@ public final class OpenGroupManager: NSObject { // MARK: - Convenience + public static func addPendingReaction( + emoji: String, + id: Int64, + in roomToken: String, + on server: String, + type: VisibleMessage.VMReaction.Kind, + using dependencies: OGMDependencies = OGMDependencies() + ) -> OpenGroupAPI.PendingChange { + let pendingChange = OpenGroupAPI.PendingChange( + server: server, + room: roomToken, + changeType: .reaction, + metadata: .reaction( + messageId: id, + emoji: emoji, + action: type + ) + ) + + dependencies.mutableCache.mutate { + $0.pendingChanges.append(pendingChange) + } + + return pendingChange + } + + public static func updatePendingChange( + _ pendingChange: OpenGroupAPI.PendingChange, + seqNo: Int64, + using dependencies: OGMDependencies = OGMDependencies() + ) { + dependencies.mutableCache.mutate { + if let index = $0.pendingChanges.firstIndex(of: pendingChange) { + $0.pendingChanges[index].seqNo = seqNo + } + } + } + /// This method specifies if the given capability is supported on a specified Open Group public static func isOpenGroupSupport( _ capability: Capability.Variant,