diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 49786dea3..9dc0ba8ed 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1086,13 +1086,19 @@ extension ConversationVC: .deleteAll(db) } else { + let sortId = Reaction.getSortId( + db, + interactionId: cellViewModel.id, + emoji: emoji + ) try Reaction( interactionId: cellViewModel.id, serverHash: nil, timestampMs: sentTimestamp, authorId: cellViewModel.currentUserPublicKey, emoji: emoji, - count: 1 // TODO: For open groups this should be '0' + count: 1, + sortId: sortId ).insert(db) // Add it to the recent list diff --git a/SessionMessagingKit/Database/Migrations/_005_EmojiReacts.swift b/SessionMessagingKit/Database/Migrations/_005_EmojiReacts.swift index 2ec472c8b..a6d57340c 100644 --- a/SessionMessagingKit/Database/Migrations/_005_EmojiReacts.swift +++ b/SessionMessagingKit/Database/Migrations/_005_EmojiReacts.swift @@ -29,6 +29,9 @@ enum _005_EmojiReacts: Migration { t.column(.count, .integer) .notNull() .defaults(to: 0) + t.column(.sortId, .integer) + .notNull() + .defaults(to: 0) /// A specific author should only be able to have a single instance of each emoji on a particular interaction t.uniqueKey([.interactionId, .emoji, .authorId]) diff --git a/SessionMessagingKit/Database/Models/Reaction.swift b/SessionMessagingKit/Database/Models/Reaction.swift index f02d0d8af..a5266e65e 100644 --- a/SessionMessagingKit/Database/Models/Reaction.swift +++ b/SessionMessagingKit/Database/Models/Reaction.swift @@ -19,6 +19,7 @@ public struct Reaction: Codable, Equatable, Hashable, FetchableRecord, Persistab case authorId case emoji case count + case sortId } /// The id for the interaction this reaction belongs to @@ -45,6 +46,9 @@ public struct Reaction: Codable, Equatable, Hashable, FetchableRecord, Persistab /// regardless of the type of conversation) public let count: Int64 + /// The id for sorting + public let sortId: Int64 + // MARK: - Relationships public var interaction: QueryInterfaceRequest { @@ -63,7 +67,8 @@ public struct Reaction: Codable, Equatable, Hashable, FetchableRecord, Persistab timestampMs: Int64, authorId: String, emoji: String, - count: Int64 + count: Int64, + sortId: Int64 ) { self.interactionId = interactionId self.serverHash = serverHash @@ -71,6 +76,7 @@ public struct Reaction: Codable, Equatable, Hashable, FetchableRecord, Persistab self.authorId = authorId self.emoji = emoji self.count = count + self.sortId = sortId } } @@ -81,7 +87,8 @@ public extension Reaction { interactionId: Int64? = nil, serverHash: String? = nil, authorId: String? = nil, - count: Int64? = nil + count: Int64? = nil, + sortId: Int64? = nil ) -> Reaction { return Reaction( interactionId: (interactionId ?? self.interactionId), @@ -89,7 +96,41 @@ public extension Reaction { timestampMs: self.timestampMs, authorId: (authorId ?? self.authorId), emoji: self.emoji, - count: (count ?? self.count) + count: (count ?? self.count), + sortId: (sortId ?? self.sortId) ) } } + +// MARK: - SortId + +public extension Reaction { + static func getSortId( + _ db: Database, + interactionId: Int64, + emoji: String + ) -> Int64 { + let existingSortId: Int64? = try? Reaction + .select(Columns.sortId) + .filter(Columns.interactionId == interactionId) + .filter(Columns.emoji == emoji) + .asRequest(of: Int64.self) + .fetchOne(db) + + if let sortId = existingSortId { + return sortId + } + + let existingLargestSortId: Int64? = try? Reaction + .select(max(Columns.sortId)) + .filter(Columns.interactionId == interactionId) + .asRequest(of: Int64.self) + .fetchOne(db) + + if let sortId = existingLargestSortId { + return sortId + 1 + } + + return 0 + } +} diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index 91bc8c819..6822f1fe0 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -106,36 +106,30 @@ extension MessageReceiveJob { case message case variant case serializedProtoData - case reactions } public let message: Message public let variant: Message.Variant public let serializedProtoData: Data - public let reactions: [Reaction] public init( message: Message, variant: Message.Variant, - proto: SNProtoContent, - reactions: [Reaction] + proto: SNProtoContent ) throws { self.message = message self.variant = variant self.serializedProtoData = try proto.serializedData() - self.reactions = reactions } private init( message: Message, variant: Message.Variant, - serializedProtoData: Data, - reactions: [Reaction] + serializedProtoData: Data ) { self.message = message self.variant = variant self.serializedProtoData = serializedProtoData - self.reactions = reactions } // MARK: - Codable @@ -151,8 +145,7 @@ extension MessageReceiveJob { self = MessageInfo( message: try variant.decode(from: container, forKey: .message), variant: variant, - serializedProtoData: try container.decode(Data.self, forKey: .serializedProtoData), - reactions: try container.decode([Reaction].self, forKey: .reactions) + serializedProtoData: try container.decode(Data.self, forKey: .serializedProtoData) ) } @@ -167,7 +160,6 @@ extension MessageReceiveJob { try container.encode(message, forKey: .message) try container.encode(variant, forKey: .variant) try container.encode(serializedProtoData, forKey: .serializedProtoData) - try container.encode(reactions, forKey: .reactions) } } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index dcede8ef5..f563fe932 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -303,21 +303,9 @@ public extension Message { throw MessageReceiverError.invalidMessage } - let reactions = processRawReceivedReactions( - db, - reactions: message.reactions, - serverExpirationTimestamp: nil, - serverHash: nil, - openGroupId: openGroupId, - openGroupMessageServerId: message.id, - openGroupServerPublicKey: openGroupServerPublicKey, - dependencies: dependencies - ) - return try processRawReceivedMessage( db, envelope: envelope, - reactions: reactions, serverExpirationTimestamp: nil, serverHash: nil, openGroupId: openGroupId, @@ -361,18 +349,14 @@ public extension Message { ) } - private static func processRawReceivedReactions( + static func processRawReceivedReactions( _ db: Database, - reactions: [String:OpenGroupAPI.Message.Reaction]?, - serverExpirationTimestamp: TimeInterval?, - serverHash: String?, openGroupId: String, - openGroupMessageServerId: Int64? = nil, - openGroupServerPublicKey: String? = nil, + message: OpenGroupAPI.Message, dependencies: SMKDependencies = SMKDependencies() ) -> [Reaction] { var results: [Reaction] = [] - guard let openGroupMessageServerId = openGroupMessageServerId, let reactions = reactions else { return results } + guard let reactions = message.reactions else { return results } let userPublicKey: String = getUserHexEncodedPublicKey(db) let blindedUserPublicKey: String? = SessionThread .getUserHexEncodedBlindedKey( @@ -385,27 +369,30 @@ public extension Message { let reactors = rawReaction.reactors { var count = rawReaction.count + let sortId: Int64 = 0 // TODO: Need to be modified to the server returned value for reactor in reactors { if reactor == blindedUserPublicKey { continue } // Will add a reaction for this case outside of the loop let reaction = Reaction( - interactionId: openGroupMessageServerId, + interactionId: message.id, serverHash: nil, timestampMs: Int64(floor((Date().timeIntervalSince1970 * 1000))), authorId: reactor, emoji: emoji, - count: count + count: count, + sortId: sortId ) count = 0 // Only insert the first reaction with the total count of this emoji results.append(reaction) } if rawReaction.you && !reactors.contains(userPublicKey) { let reaction = Reaction( - interactionId: openGroupMessageServerId, + interactionId: message.id, serverHash: nil, timestampMs: Int64(floor((Date().timeIntervalSince1970 * 1000))), authorId: userPublicKey, emoji: emoji, - count: 0 + count: count, + sortId: sortId ) results.append(reaction) } @@ -417,7 +404,6 @@ public extension Message { private static func processRawReceivedMessage( _ db: Database, envelope: SNProtoEnvelope, - reactions: [Reaction] = [], serverExpirationTimestamp: TimeInterval?, serverHash: String?, openGroupId: String? = nil, @@ -487,8 +473,7 @@ public extension Message { try MessageReceiveJob.Details.MessageInfo( message: message, variant: variant, - proto: proto, - reactions: reactions + proto: proto ) ) } diff --git a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift index 1bf648d0a..b9ff94939 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift @@ -94,7 +94,7 @@ extension OpenGroupAPI.Message { self = OpenGroupAPI.Message( id: try container.decode(Int64.self, forKey: .id), sender: try? container.decode(String.self, forKey: .sender), - posted: try container.decode(TimeInterval.self, forKey: .posted), + posted: ((try? container.decode(TimeInterval.self, forKey: .posted)) ?? Date().timeIntervalSince1970), // Reaction updates don't include posted edited: try? container.decode(TimeInterval.self, forKey: .edited), seqNo: try container.decode(Int64.self, forKey: .seqNo), whisper: ((try? container.decode(Bool.self, forKey: .whisper)) ?? false), diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index a97b84fdd..c1b228a24 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -512,48 +512,72 @@ public final class OpenGroupManager: NSObject { // Process the messages sortedMessages.forEach { message in - guard - let base64EncodedString: String = message.base64EncodedData, - let data = Data(base64Encoded: base64EncodedString) - else { + if message.base64EncodedData == nil && message.reactions == nil { // A message with no data has been deleted so add it to the list to remove messageServerIdsToRemove.append(UInt64(message.id)) return } - do { - let processedMessage: ProcessedMessage? = try Message.processReceivedOpenGroupMessage( - db, - openGroupId: openGroup.id, - openGroupServerPublicKey: openGroup.publicKey, - message: message, - data: data, - dependencies: dependencies - ) - - if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo { - try MessageReceiver.handle( + // Handle messages + if let base64EncodedString: String = message.base64EncodedData, + let data = Data(base64Encoded: base64EncodedString) + { + do { + let processedMessage: ProcessedMessage? = try Message.processReceivedOpenGroupMessage( db, - message: messageInfo.message, - associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData), openGroupId: openGroup.id, - openGroupReactions: messageInfo.reactions, - isBackgroundPoll: isBackgroundPoll, + openGroupServerPublicKey: openGroup.publicKey, + message: message, + data: data, dependencies: dependencies ) + + if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo { + try MessageReceiver.handle( + db, + message: messageInfo.message, + associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData), + openGroupId: openGroup.id, + isBackgroundPoll: isBackgroundPoll, + dependencies: dependencies + ) + } + } + catch { + switch error { + // Ignore duplicate & selfSend message errors (and don't bother logging + // them as there will be a lot since we each service node duplicates messages) + case DatabaseError.SQLITE_CONSTRAINT_UNIQUE, + MessageReceiverError.duplicateMessage, + MessageReceiverError.duplicateControlMessage, + MessageReceiverError.selfSend: + break + + default: SNLog("Couldn't receive open group message due to error: \(error).") + } } } - catch { - switch error { - // Ignore duplicate & selfSend message errors (and don't bother logging - // them as there will be a lot since we each service node duplicates messages) - case DatabaseError.SQLITE_CONSTRAINT_UNIQUE, - MessageReceiverError.duplicateMessage, - MessageReceiverError.duplicateControlMessage, - MessageReceiverError.selfSend: - break + + // Handle reactions + if message.reactions != nil { + do { + let reactions: [Reaction] = Message.processRawReceivedReactions( + db, + openGroupId: openGroup.id, + message: message, + dependencies: dependencies + ) - default: SNLog("Couldn't receive open group message due to error: \(error).") + if !reactions.isEmpty { + try MessageReceiver.handleOpenGroupReactions( + db, + openGroupMessageServerId: message.id, + openGroupReactions: reactions + ) + } + } + catch { + SNLog("Couldn't handle open group reactions due to error: \(error).") } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index f92ce527b..b028be09d 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -12,7 +12,6 @@ extension MessageReceiver { message: VisibleMessage, associatedWithProto proto: SNProtoContent, openGroupId: String?, - openGroupReactions: [Reaction] = [], isBackgroundPoll: Bool, dependencies: Dependencies = Dependencies() ) throws -> Int64 { @@ -141,10 +140,6 @@ extension MessageReceiver { return recipientParts[2] }() ).inserted(db) - - for reaction in openGroupReactions { - try reaction.with(interactionId: interaction.id).insert(db) - } } catch { switch error { @@ -329,6 +324,12 @@ extension MessageReceiver { throw StorageError.objectNotFound } + let sortId = Reaction.getSortId( + db, + interactionId: interactionId, + emoji: reaction.emoji + ) + switch reaction.kind { case .react: try Reaction( @@ -337,7 +338,8 @@ extension MessageReceiver { timestampMs: Int64(messageSentTimestamp * 1000), authorId: sender, emoji: reaction.emoji, - count: 1 // TODO: Handle Open Group case + count: 1, + sortId: sortId ).insert(db) case .remove: diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index e97b4544d..21df40614 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -180,7 +180,6 @@ public enum MessageReceiver { message: Message, associatedWithProto proto: SNProtoContent, openGroupId: String?, - openGroupReactions: [Reaction] = [], isBackgroundPoll: Bool, dependencies: SMKDependencies = SMKDependencies() ) throws { @@ -218,7 +217,6 @@ public enum MessageReceiver { message: message, associatedWithProto: proto, openGroupId: openGroupId, - openGroupReactions: openGroupReactions, isBackgroundPoll: isBackgroundPoll ) @@ -251,6 +249,29 @@ public enum MessageReceiver { } } + public static func handleOpenGroupReactions( + _ db: Database, + openGroupMessageServerId: Int64, + openGroupReactions: [Reaction] + ) throws { + guard let interactionId: Int64 = try? Interaction + .select(.id) + .filter(Interaction.Columns.openGroupServerMessageId == openGroupMessageServerId) + .asRequest(of: Int64.self) + .fetchOne(db) + else { + throw MessageReceiverError.invalidMessage + } + + _ = try Reaction + .filter(Reaction.Columns.interactionId == interactionId) + .deleteAll(db) + + for reaction in openGroupReactions { + try reaction.with(interactionId: interactionId).insert(db) + } + } + // MARK: - Convenience internal static func threadInfo(_ db: Database, message: Message, openGroupId: String?) -> (id: String, variant: SessionThread.Variant)? {