From aaf66935bc4e51e8aa883e7219030ea238434e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Thu, 8 Aug 2024 14:12:08 +0200 Subject: [PATCH] Adjust membership to use partial updates from PubNub --- pubnub-chat-api/api/pubnub-chat-api.api | 5 +- .../kotlin/com/pubnub/chat/Membership.kt | 3 ++ .../com/pubnub/chat/internal/ChatImpl.kt | 8 +-- .../pubnub/chat/internal/MembershipImpl.kt | 52 +++++++++++++------ .../internal/channel/ThreadChannelImpl.kt | 2 +- .../chat/internal/message/BaseMessage.kt | 9 +++- .../internal/message/ThreadMessageImpl.kt | 2 +- .../internal/restrictions/RestrictionImpl.kt | 21 +++----- .../integration/MembershipIntegrationTest.kt | 23 ++++---- pubnub-kotlin | 2 +- 10 files changed, 78 insertions(+), 49 deletions(-) diff --git a/pubnub-chat-api/api/pubnub-chat-api.api b/pubnub-chat-api/api/pubnub-chat-api.api index 3b43177f..197926e5 100644 --- a/pubnub-chat-api/api/pubnub-chat-api.api +++ b/pubnub-chat-api/api/pubnub-chat-api.api @@ -12,6 +12,7 @@ public abstract interface class com/pubnub/chat/Channel { public abstract fun getId ()Ljava/lang/String; public abstract fun getMembers (Ljava/lang/Integer;Lcom/pubnub/api/models/consumer/objects/PNPage;Ljava/lang/String;Ljava/util/Collection;)Lcom/pubnub/kmp/PNFuture; public abstract fun getMessage (J)Lcom/pubnub/kmp/PNFuture; + public abstract fun getMessageReportsHistory (Ljava/lang/Long;Ljava/lang/Long;I)Lcom/pubnub/kmp/PNFuture; public abstract fun getName ()Ljava/lang/String; public abstract fun getPinnedMessage ()Lcom/pubnub/kmp/PNFuture; public abstract fun getStatus ()Ljava/lang/String; @@ -33,6 +34,7 @@ public abstract interface class com/pubnub/chat/Channel { public abstract fun setRestrictions (Lcom/pubnub/chat/User;ZZLjava/lang/String;)Lcom/pubnub/kmp/PNFuture; public abstract fun startTyping ()Lcom/pubnub/kmp/PNFuture; public abstract fun stopTyping ()Lcom/pubnub/kmp/PNFuture; + public abstract fun streamMessageReports (Lkotlin/jvm/functions/Function1;)Ljava/lang/AutoCloseable; public abstract fun streamPresence (Lkotlin/jvm/functions/Function1;)Ljava/lang/AutoCloseable; public abstract fun streamReadReceipts (Lkotlin/jvm/functions/Function1;)Ljava/lang/AutoCloseable; public abstract fun streamUpdates (Lkotlin/jvm/functions/Function1;)Ljava/lang/AutoCloseable; @@ -50,6 +52,7 @@ public final class com/pubnub/chat/Channel$DefaultImpls { public static synthetic fun getFiles$default (Lcom/pubnub/chat/Channel;ILjava/lang/String;ILjava/lang/Object;)Lcom/pubnub/kmp/PNFuture; public static synthetic fun getHistory$default (Lcom/pubnub/chat/Channel;Ljava/lang/Long;Ljava/lang/Long;IILjava/lang/Object;)Lcom/pubnub/kmp/PNFuture; public static synthetic fun getMembers$default (Lcom/pubnub/chat/Channel;Ljava/lang/Integer;Lcom/pubnub/api/models/consumer/objects/PNPage;Ljava/lang/String;Ljava/util/Collection;ILjava/lang/Object;)Lcom/pubnub/kmp/PNFuture; + public static synthetic fun getMessageReportsHistory$default (Lcom/pubnub/chat/Channel;Ljava/lang/Long;Ljava/lang/Long;IILjava/lang/Object;)Lcom/pubnub/kmp/PNFuture; public static synthetic fun getUserSuggestions$default (Lcom/pubnub/chat/Channel;Ljava/lang/String;IILjava/lang/Object;)Lcom/pubnub/kmp/PNFuture; public static synthetic fun getUsersRestrictions$default (Lcom/pubnub/chat/Channel;Ljava/lang/Integer;Lcom/pubnub/api/models/consumer/objects/PNPage;Ljava/util/Collection;ILjava/lang/Object;)Lcom/pubnub/kmp/PNFuture; public static synthetic fun join$default (Lcom/pubnub/chat/Channel;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/pubnub/kmp/PNFuture; @@ -139,6 +142,7 @@ public abstract interface class com/pubnub/chat/Membership { public abstract fun getUnreadMessagesCount ()Lcom/pubnub/kmp/PNFuture; public abstract fun getUpdated ()Ljava/lang/String; public abstract fun getUser ()Lcom/pubnub/chat/User; + public abstract fun plus (Lcom/pubnub/api/models/consumer/pubsub/objects/PNSetMembershipEvent;)Lcom/pubnub/chat/Membership; public abstract fun setLastReadMessage (Lcom/pubnub/chat/Message;)Lcom/pubnub/kmp/PNFuture; public abstract fun setLastReadMessageTimetoken (J)Lcom/pubnub/kmp/PNFuture; public abstract fun streamUpdates (Lkotlin/jvm/functions/Function1;)Ljava/lang/AutoCloseable; @@ -235,7 +239,6 @@ public abstract interface class com/pubnub/chat/User { public abstract fun getUpdated ()Ljava/lang/String; public abstract fun isPresentOn (Ljava/lang/String;)Lcom/pubnub/kmp/PNFuture; public abstract fun plus (Lcom/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadata;)Lcom/pubnub/chat/User; - public abstract fun report (Ljava/lang/String;)Lcom/pubnub/kmp/PNFuture; public abstract fun setRestrictions (Lcom/pubnub/chat/Channel;ZZLjava/lang/String;)Lcom/pubnub/kmp/PNFuture; public abstract fun streamUpdates (Lkotlin/jvm/functions/Function1;)Ljava/lang/AutoCloseable; public abstract fun update (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)Lcom/pubnub/kmp/PNFuture; diff --git a/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt b/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt index e7ab9146..5af1bd10 100644 --- a/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt +++ b/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt @@ -1,5 +1,6 @@ package com.pubnub.chat +import com.pubnub.api.models.consumer.pubsub.objects.PNSetMembershipEvent import com.pubnub.kmp.CustomObject import com.pubnub.kmp.PNFuture @@ -25,5 +26,7 @@ interface Membership { // todo do we have test for this? fun streamUpdates(callback: (membership: Membership?) -> Unit): AutoCloseable + operator fun plus(update: PNSetMembershipEvent): Membership + companion object } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt index e0b87339..18f44774 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt @@ -708,7 +708,7 @@ class ChatImpl( override fun getThreadChannel(message: Message): PNFuture { val threadChannelId = getThreadId(message.channelId, message.timetoken) return pubNub.getChannelMetadata(threadChannelId).then { - ThreadChannelImpl.fromDTO(this, message, it.data!!) + ThreadChannelImpl.fromDTO(this, message, it.data) }.catch { if (it is PubNubException && it.statusCode == HTTP_ERROR_404) { Result.failure(PubNubException(THIS_MESSAGE_IS_NOT_A_THREAD, it)) @@ -1195,11 +1195,7 @@ class ChatImpl( custom = createCustomObject(customWithUpdatedLastActiveTimestamp), includeCustom = true, ).then { pnUUIDMetadataResult: PNUUIDMetadataResult -> - if (pnUUIDMetadataResult.data != null) { - currentUser = UserImpl.fromDTO(this, pnUUIDMetadataResult.data!!) - } else { - log.pnError(PNUUID_METADATA_IS_NULL) - } + currentUser = UserImpl.fromDTO(this, pnUUIDMetadataResult.data) } } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt index d62c22ff..32d8c858 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt @@ -4,6 +4,7 @@ import com.pubnub.api.models.consumer.objects.member.PNMember import com.pubnub.api.models.consumer.objects.membership.PNChannelDetailsLevel import com.pubnub.api.models.consumer.objects.membership.PNChannelMembership import com.pubnub.api.models.consumer.pubsub.objects.PNDeleteMembershipEventMessage +import com.pubnub.api.models.consumer.pubsub.objects.PNSetMembershipEvent import com.pubnub.api.models.consumer.pubsub.objects.PNSetMembershipEventMessage import com.pubnub.chat.Channel import com.pubnub.chat.Membership @@ -100,6 +101,17 @@ data class MembershipImpl( } } + override fun plus(update: PNSetMembershipEvent): Membership { + return MembershipImpl( + chat, + channel, + user, + update.custom?.value ?: custom, + update.updated, + update.eTag + ) + } + private fun exists(): PNFuture = chat.pubNub.getMemberships(uuid = user.id, filter = filterThisChannel()).then { it.data.isNotEmpty() @@ -128,20 +140,30 @@ data class MembershipImpl( val membership = memberships.find { it.channel.id == event.channel && it.user.id == eventUuid } ?: return@createEventListener val newMembership = when (val message = event.extractedMessage) { - is PNSetMembershipEventMessage -> MembershipImpl( - chat, - user = membership.user, - channel = membership.channel, - custom = message.data.custom, - updated = message.data.updated, - eTag = message.data.eTag - ) + is PNSetMembershipEventMessage -> { + val previousMembership = latestMemberships.find { it.channel.id == event.channel && it.user.id == eventUuid } + previousMembership?.let { it + message.data } + ?: MembershipImpl( + chat, + user = membership.user, + channel = membership.channel, + custom = message.data.custom?.value, + updated = message.data.updated, + eTag = message.data.eTag + ) + } is PNDeleteMembershipEventMessage -> null else -> return@createEventListener } - latestMemberships = latestMemberships.asSequence().filter { - it.channel.id != event.channel || it.user.id != eventUuid - }.run { newMembership?.let { plus(it) } ?: this }.toList() + latestMemberships = latestMemberships.asSequence().filter { membership -> + membership.channel.id != event.channel || membership.user.id != eventUuid + }.let { sequence -> + if (newMembership != null) { + sequence + newMembership + } else { + sequence + } + }.toList() callback(latestMemberships) }) @@ -154,9 +176,9 @@ data class MembershipImpl( internal fun fromMembershipDTO(chat: ChatInternal, channelMembership: PNChannelMembership, user: User) = MembershipImpl( chat, - ChannelImpl.fromDTO(chat, channelMembership.channel!!), + ChannelImpl.fromDTO(chat, channelMembership.channel), user, - channelMembership.custom, + channelMembership.custom?.value, channelMembership.updated, channelMembership.eTag ) @@ -165,8 +187,8 @@ data class MembershipImpl( MembershipImpl( chat, channel, - UserImpl.fromDTO(chat, userMembership.uuid!!), - userMembership.custom, + UserImpl.fromDTO(chat, userMembership.uuid), + userMembership.custom?.value, userMembership.updated, userMembership.eTag, ) diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/ThreadChannelImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/ThreadChannelImpl.kt index f45ee8b4..7d32914c 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/ThreadChannelImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/ThreadChannelImpl.kt @@ -69,7 +69,7 @@ data class ThreadChannelImpl( log.pnError(PARENT_CHANNEL_DOES_NOT_EXISTS) } ChatImpl.pinMessageToChannel(chat.pubNub, message, parentChannel).then { - ChannelImpl.fromDTO(chat, it.data!!) + ChannelImpl.fromDTO(chat, it.data) } } } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt index 6bd5f868..24e3df67 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt @@ -18,8 +18,10 @@ import com.pubnub.chat.internal.METADATA_REFERENCED_CHANNELS import com.pubnub.chat.internal.METADATA_TEXT_LINKS import com.pubnub.chat.internal.THREAD_ROOT_ID import com.pubnub.chat.internal.channel.ChannelImpl +import com.pubnub.chat.internal.error.PubNubErrorMessage import com.pubnub.chat.internal.error.PubNubErrorMessage.CANNOT_STREAM_MESSAGE_UPDATES_ON_EMPTY_LIST import com.pubnub.chat.internal.serialization.PNDataEncoder +import com.pubnub.chat.internal.util.logErrorAndReturnException import com.pubnub.chat.internal.util.pnError import com.pubnub.chat.types.EventContent import com.pubnub.chat.types.File @@ -162,8 +164,11 @@ abstract class BaseMessage( override fun pin(): PNFuture { return chat.getChannel(channelId).thenAsync { channel -> - ChatImpl.pinMessageToChannel(chat.pubNub, this, channel!!).then { - ChannelImpl.fromDTO(chat, it.data!!) + if (channel == null) { + throw log.logErrorAndReturnException(PubNubErrorMessage.CHANNEL_NOT_EXIST) + } + ChatImpl.pinMessageToChannel(chat.pubNub, this, channel).then { + ChannelImpl.fromDTO(chat, it.data) } } } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/ThreadMessageImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/ThreadMessageImpl.kt index e524eb55..2506f26e 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/ThreadMessageImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/ThreadMessageImpl.kt @@ -102,7 +102,7 @@ data class ThreadMessageImpl( log.pnError(PARENT_CHANNEL_DOES_NOT_EXISTS) } ChatImpl.pinMessageToChannel(chat.pubNub, message, parentChannel).then { - ChannelImpl.fromDTO(chat, it.data!!) + ChannelImpl.fromDTO(chat, it.data) } } } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/restrictions/RestrictionImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/restrictions/RestrictionImpl.kt index 97e7b1da..daf914ad 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/restrictions/RestrictionImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/restrictions/RestrictionImpl.kt @@ -3,9 +3,6 @@ package com.pubnub.chat.internal.restrictions import com.pubnub.api.models.consumer.objects.member.PNMember import com.pubnub.api.models.consumer.objects.membership.PNChannelMembership import com.pubnub.chat.internal.INTERNAL_MODERATION_PREFIX -import com.pubnub.chat.internal.error.PubNubErrorMessage.CHANNEL_ID_MUST_BE_DEFINED -import com.pubnub.chat.internal.error.PubNubErrorMessage.NO_SUCH_MEMBERSHIP_EXISTS -import com.pubnub.chat.internal.util.pnError import com.pubnub.chat.restrictions.Restriction import org.lighthousegames.logging.logging @@ -15,12 +12,10 @@ class RestrictionImpl { fun fromChannelMembershipDTO(userId: String, pnChannelMembership: PNChannelMembership): Restriction { val channelId = - pnChannelMembership.channel?.id?.substringAfter(INTERNAL_MODERATION_PREFIX) ?: log.pnError( - CHANNEL_ID_MUST_BE_DEFINED - ) - val customData: Map? = pnChannelMembership.custom - val ban: Boolean = (customData?.get("ban") as? Boolean) ?: false - val mute: Boolean = (customData?.get("mute") as? Boolean) ?: false + pnChannelMembership.channel.id.substringAfter(INTERNAL_MODERATION_PREFIX) + val customData: Map? = pnChannelMembership.custom?.value + val ban: Boolean = (customData?.get("ban") as? Boolean) == true + val mute: Boolean = (customData?.get("mute") as? Boolean) == true val reason: String? = customData?.get("reason")?.toString() return Restriction( @@ -33,10 +28,10 @@ class RestrictionImpl { } fun fromMemberDTO(channelId: String, pnMember: PNMember): Restriction { - val userId = pnMember.uuid?.id ?: log.pnError(NO_SUCH_MEMBERSHIP_EXISTS) - val customData: Map? = pnMember.custom - val ban: Boolean = (customData?.get("ban") as? Boolean) ?: false - val mute: Boolean = (customData?.get("mute") as? Boolean) ?: false + val userId = pnMember.uuid.id + val customData: Map? = pnMember.custom?.value + val ban: Boolean = (customData?.get("ban") as? Boolean) == true + val mute: Boolean = (customData?.get("mute") as? Boolean) == true val reason: String? = customData?.get("reason")?.toString() return Restriction( userId = userId, diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/MembershipIntegrationTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/MembershipIntegrationTest.kt index 414fab33..2ad31210 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/MembershipIntegrationTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/MembershipIntegrationTest.kt @@ -35,18 +35,16 @@ class MembershipIntegrationTest : BaseChatIntegrationTest() { channel02.status ).await() delayInMillis(1000) - val membership1 = channel01.join { }.await().also { it.disconnect?.close() }.membership - val membership2 = channel02.join { }.await().also { it.disconnect?.close() }.membership + val membership1 = channel01.join().await().membership + val membership2 = channel02.join().await().membership delayInMillis(1000) val expectedUpdates = listOf>( listOf( - membership1.asImpl().copy(custom = mapOf("a" to "b"), updated = null, eTag = null), - membership2.asImpl().copy(updated = null, eTag = null) - ).sortedBy { - it.channel.id - }, - listOf(membership1.asImpl().copy(custom = mapOf("a" to "b"), updated = null, eTag = null)).sortedBy { it.channel.id }, + membership1.asImpl().copy(custom = mapOf("a" to "b")), + membership2.asImpl().copy() + ), + listOf(membership1.asImpl().copy(custom = mapOf("a" to "b"))), emptyList() ) val actualUpdates = mutableListOf>() @@ -65,7 +63,14 @@ class MembershipIntegrationTest : BaseChatIntegrationTest() { delayInMillis(1000) dispose?.close() } - assertEquals(expectedUpdates, actualUpdates) + assertEquals( + expectedUpdates.map { membershipList -> + membershipList.map { membership -> + membership.asImpl().copy(updated = null, eTag = null) as Membership + }.sortedBy { it.channel.id } + }, + actualUpdates + ) } @Test diff --git a/pubnub-kotlin b/pubnub-kotlin index f5d37b8b..e47ee8d7 160000 --- a/pubnub-kotlin +++ b/pubnub-kotlin @@ -1 +1 @@ -Subproject commit f5d37b8bf748f7505d3c49483eee064b5b95414b +Subproject commit e47ee8d7a3121238755b6e4f2baaa64463c6b606