diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index c4c562da6e..bdfaa029cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -757,6 +757,9 @@ class GroupManagerV2Impl @Inject constructor( configs.groupMembers.set(member) } } + + // Remove lastHash so we can receive all the messages in the past + lokiAPIDatabase.clearLastMessageHashes(groupId.hexString) } // Delete the promotion message remotely diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt index 41784d1caa..f2700049ff 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt @@ -154,7 +154,14 @@ class Poller( val namespace = forConfig.namespace val processed = if (!messages.isNullOrEmpty()) { SnodeAPI.updateLastMessageHashValueIfPossible(snode, userPublicKey, messages, namespace) - SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { rawMessageAsJSON -> + SnodeAPI.removeDuplicates( + publicKey = userPublicKey, + messages = messages, + messageHashGetter = { (it as? Map<*, *>)?.get("hash") as? String }, + namespace = namespace, + updateStoredHashes = true + ).mapNotNull { rawMessageAsJSON -> + rawMessageAsJSON as Map<*, *> // removeDuplicates should have ensured this is always a map val hashValue = rawMessageAsJSON["hash"] as? String ?: return@mapNotNull null val b64EncodedBody = rawMessageAsJSON["data"] as? String ?: return@mapNotNull null val timestamp = rawMessageAsJSON["t"] as? Long ?: SnodeAPI.nowWithOffset diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 68b5f7914f..5b9860536e 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -12,14 +12,11 @@ import com.goterl.lazysodium.utils.Key import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch import kotlinx.coroutines.selects.select -import com.goterl.lazysodium.utils.KeyPair -import kotlinx.coroutines.coroutineScope import nl.komponents.kovenant.Promise import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.bind @@ -986,7 +983,13 @@ object SnodeAPI { fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0, updateLatestHash: Boolean = true, updateStoredHashes: Boolean = true, decrypt: ((ByteArray) -> Pair?)? = null): List> = (rawResponse["messages"] as? List<*>)?.let { messages -> if (updateLatestHash) updateLastMessageHashValueIfPossible(snode, publicKey, messages, namespace) - parseEnvelopes(removeDuplicates(publicKey, messages, namespace, updateStoredHashes), decrypt) + removeDuplicates( + publicKey = publicKey, + messages = parseEnvelopes(messages, decrypt), + messageHashGetter = { it.second }, + namespace = namespace, + updateStoredHashes = updateStoredHashes + ) } ?: listOf() fun updateLastMessageHashValueIfPossible(snode: Snode, publicKey: String, rawMessages: List<*>, namespace: Int) { @@ -1005,17 +1008,35 @@ object SnodeAPI { * database#setReceivedMessageHashValues is only called here. */ @Synchronized - fun removeDuplicates(publicKey: String, rawMessages: List<*>, namespace: Int, updateStoredHashes: Boolean): List> { + fun removeDuplicates( + publicKey: String, + messages: List, + messageHashGetter: (M) -> String?, + namespace: Int, + updateStoredHashes: Boolean + ): List { val hashValues = database.getReceivedMessageHashValues(publicKey, namespace)?.toMutableSet() ?: mutableSetOf() - return rawMessages.filterIsInstance>().filter { rawMessage -> - val hash = rawMessage["hash"] as? String - hash ?: Log.d("Loki", "Missing hash value for message: ${rawMessage.prettifiedDescription()}.") - hash?.let(hashValues::add) == true - }.also { - if (updateStoredHashes && it.isNotEmpty()) { - database.setReceivedMessageHashValues(publicKey, hashValues, namespace) + return messages + .filter { message -> + val hash = messageHashGetter(message) + if (hash == null) { + Log.d("Loki", "Missing hash value for message: ${message?.prettifiedDescription()}.") + return@filter false + } + + val isNew = hashValues.add(hash) + + if (!isNew) { + Log.d("Loki", "Duplicate message hash: $hash.") + } + + isNew + } + .also { + if (updateStoredHashes && it.isNotEmpty()) { + database.setReceivedMessageHashValues(publicKey, hashValues, namespace) + } } - } } private fun parseEnvelopes(rawMessages: List<*>, decrypt: ((ByteArray)->Pair?)?): List> {