Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial updates for memberships, remove implicit "disconnect" from Channel.leave #55

Merged
merged 3 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pubnub-chat-api/api/pubnub-chat-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ class ChatImpl(
override fun getThreadChannel(message: Message): PNFuture<ThreadChannel> {
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))
Expand Down Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Boolean> =
chat.pubNub.getMemberships(uuid = user.id, filter = filterThisChannel()).then {
it.data.isNotEmpty()
Expand Down Expand Up @@ -128,20 +140,32 @@ 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)
})

Expand All @@ -154,9 +178,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
)
Expand All @@ -165,8 +189,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,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ abstract class BaseChannel<C : Channel, M : Message>(
val messageFactory: (ChatInternal, PNFetchMessageItem, channelId: String) -> M,
) : Channel {
private val suggestedMemberships = mutableMapOf<String, Set<Membership>>()
private var disconnect: AutoCloseable? = null
private var typingSent: Instant? = null
private val sendTextRateLimiter by lazy {
ExponentialRateLimiter(
Expand Down Expand Up @@ -439,13 +438,7 @@ abstract class BaseChannel<C : Channel, M : Message>(
includeType = true,
filter = channelFilterString,
).thenAsync { membershipArray: PNChannelMembershipArrayResult ->
val resultDisconnect = if (callback != null) {
connect(callback).also {
disconnect = it // todo the whole disconnect handling is not safe! state can be made inconsistent
}
} else {
null
}
val resultDisconnect = callback?.let { connect(it) }

chat.pubNub.time().thenAsync { time: PNTimeResult ->
MembershipImpl.fromMembershipDTO(chat, membershipArray.data.first(), user)
Expand All @@ -459,11 +452,7 @@ abstract class BaseChannel<C : Channel, M : Message>(
}
}

override fun leave(): PNFuture<Unit> = PNFuture { callback ->
disconnect?.close()
disconnect = null
callback.accept(Result.success(Unit))
}.alsoAsync { chat.pubNub.removeMemberships(channels = listOf(id)) }
override fun leave(): PNFuture<Unit> = chat.pubNub.removeMemberships(channels = listOf(id)).then { Unit }
marcin-cebo marked this conversation as resolved.
Show resolved Hide resolved

override fun getPinnedMessage(): PNFuture<Message?> {
val pinnedMessageTimetoken = this.custom?.get("pinnedMessageTimetoken").tryLong() ?: return null.asFuture()
Expand All @@ -489,11 +478,11 @@ abstract class BaseChannel<C : Channel, M : Message>(
override fun unregisterFromPush() = chat.unregisterPushChannels(listOf(id))

override fun pinMessage(message: Message): PNFuture<C> {
return pinMessageToChannel(chat.pubNub, message, this).then { channelFactory(chat, it.data!!) }
return pinMessageToChannel(chat.pubNub, message, this).then { channelFactory(chat, it.data) }
}

override fun unpinMessage(): PNFuture<C> {
return pinMessageToChannel(chat.pubNub, null, this).then { channelFactory(chat, it.data!!) }
return pinMessageToChannel(chat.pubNub, null, this).then { channelFactory(chat, it.data) }
}

override fun getUsersRestrictions(
Expand Down Expand Up @@ -657,7 +646,7 @@ abstract class BaseChannel<C : Channel, M : Message>(
}

override fun streamMessageReports(callback: (event: Event<EventContent.Report>) -> Unit): AutoCloseable {
val channelId = "${INTERNAL_MODERATION_PREFIX}${id}"
val channelId = "${INTERNAL_MODERATION_PREFIX}$id"
return chat.listenForEvents<EventContent.Report>(channelId = channelId, callback = callback)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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.pnError
Expand Down Expand Up @@ -162,8 +163,11 @@ abstract class BaseMessage<T : Message>(

override fun pin(): PNFuture<Channel> {
return chat.getChannel(channelId).thenAsync { channel ->
ChatImpl.pinMessageToChannel(chat.pubNub, this, channel!!).then {
ChannelImpl.fromDTO(chat, it.data!!)
if (channel == null) {
log.pnError(PubNubErrorMessage.CHANNEL_NOT_EXIST)
}
ChatImpl.pinMessageToChannel(chat.pubNub, this, channel).then {
ChannelImpl.fromDTO(chat, it.data)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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<String, Any?>? = 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<String, Any?>? = 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(
Expand All @@ -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<String, Any?>? = 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<String, Any?>? = 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import com.pubnub.kmp.createCustomObject
import com.pubnub.test.await
import com.pubnub.test.randomString
import com.pubnub.test.test
import junit.framework.TestCase.assertNotNull
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.test.runTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<Membership>>(
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<List<Membership>>()
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pubnub-kotlin
Submodule pubnub-kotlin updated 18 files
+4 −3 pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/objects/member/PNMember.kt
+3 −2 ...nub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/objects/membership/PNChannelMembership.kt
+1 −1 ...e/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/objects/uuid/PNUUIDMetadataResult.kt
+3 −2 .../pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/pubsub/objects/PNObjectEventResult.kt
+12 −12 pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/membership/step/WhenSteps.kt
+2 −2 pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNMembers.java
+2 −2 ...-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java
+2 −2 pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/v2/callbacks/DelegatingEventListener.java
+1 −1 pubnub-gson/pubnub-gson-impl/src/test/kotlin/com/pubnub/internal/v2/callbacks/DelegatingEventListenerTest.kt
+15 −3 ...ub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/member/PNMember.kt
+28 −2 ...b-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/membership/PNChannelMembership.kt
+1 −1 ...pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadataResult.kt
+3 −2 ...ubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNObjectEventResult.kt
+39 −0 ...tlin-api/src/commonTest/kotlin/com/pubnub/api/models/consumer/objects/membership/PNChannelMembershipTest.kt
+5 −5 pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/MembersTest.kt
+5 −5 pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/MembershipsTest.kt
+12 −0 pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/PushTest.kt
+5 −4 ...b-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/ObjectsIntegrationTest.kt
Loading