From 71ac75963e7cf98804bf5a6810365ba5247e14e1 Mon Sep 17 00:00:00 2001 From: "marcin.cebo" Date: Wed, 7 Aug 2024 16:58:36 +0200 Subject: [PATCH 1/4] Added Chat.init test --- build.gradle.kts | 7 +++++ .../compubnub/chat/ChatIntegrationTest.kt | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7191f392..a6843a23 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,13 @@ kotlin { implementation("com.pubnub:pubnub-kotlin-impl:9.2-DEV") } } + + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation("com.pubnub:pubnub-kotlin-test") + } + } } if (enableAnyIosTarget) { diff --git a/src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt b/src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt new file mode 100644 index 00000000..673e9265 --- /dev/null +++ b/src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt @@ -0,0 +1,29 @@ +package compubnub.chat + +import com.pubnub.api.PubNubException +import com.pubnub.api.UserId +import com.pubnub.api.v2.PNConfiguration +import com.pubnub.api.v2.callbacks.Result +import com.pubnub.chat.Chat +import com.pubnub.chat.config.ChatConfiguration +import com.pubnub.chat.config.LogLevel +import com.pubnub.chat.init +import kotlin.test.Test + +class ChatIntegrationTest { + + @Test + fun canInitializeChatWithLogLevel() { + val chatConfig = ChatConfiguration(logLevel = LogLevel.INFO) + val pnConfiguration = PNConfiguration.builder(userId = UserId("myUserId"), subscribeKey = "mySubscribeKey").build() + + Chat.init(chatConfig, pnConfiguration).async { result: Result -> + result.onSuccess { chat: Chat -> + println("Chat successfully initialized having logLevel: ${chatConfig.logLevel}") + }.onFailure { exception: PubNubException -> + println("Exception initialising chat: ${exception.message}") + } + } + + } +} From 0123bc49f4b4d8fb3e0c4dfac276de9d50eab437 Mon Sep 17 00:00:00 2001 From: "marcin.cebo" Date: Fri, 16 Aug 2024 15:45:26 +0200 Subject: [PATCH 2/4] Added message restore method. --- pubnub-chat-api/api/pubnub-chat-api.api | 1 + .../kotlin/com/pubnub/chat/Message.kt | 2 + .../com/pubnub/chat/internal/ChatImpl.kt | 80 ++++++++++++------- .../com/pubnub/chat/internal/ChatInternal.kt | 12 +++ .../chat/internal/channel/BaseChannel.kt | 1 + .../internal/channel/ThreadChannelImpl.kt | 19 ++++- .../chat/internal/error/PubNubErrorMessage.kt | 2 + .../chat/internal/message/BaseMessage.kt | 79 +++++++++++++++--- .../com/pubnub/chat/internal/util/Utils.kt | 6 +- .../integration/MessageIntegrationTest.kt | 33 ++++++++ .../kotlin/com/pubnub/kmp/ChatTest.kt | 2 +- .../kotlin/com/pubnub/kmp/MessageTest.kt | 60 ++++++++++++++ .../kotlin/com/pubnub/kmp/utils/FakeChat.kt | 15 ++++ .../compubnub/chat/ChatIntegrationTest.kt | 4 +- 14 files changed, 271 insertions(+), 45 deletions(-) create mode 100644 pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MessageTest.kt diff --git a/pubnub-chat-api/api/pubnub-chat-api.api b/pubnub-chat-api/api/pubnub-chat-api.api index 197926e5..7bc2f7cd 100644 --- a/pubnub-chat-api/api/pubnub-chat-api.api +++ b/pubnub-chat-api/api/pubnub-chat-api.api @@ -180,6 +180,7 @@ public abstract interface class com/pubnub/chat/Message { public abstract fun pin ()Lcom/pubnub/kmp/PNFuture; public abstract fun removeThread ()Lcom/pubnub/kmp/PNFuture; public abstract fun report (Ljava/lang/String;)Lcom/pubnub/kmp/PNFuture; + public abstract fun restore ()Lcom/pubnub/kmp/PNFuture; public abstract fun streamUpdates (Lkotlin/jvm/functions/Function1;)Ljava/lang/AutoCloseable; public abstract fun toggleReaction (Ljava/lang/String;)Lcom/pubnub/kmp/PNFuture; } diff --git a/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Message.kt b/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Message.kt index a50d087b..b46a7638 100644 --- a/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Message.kt +++ b/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Message.kt @@ -56,5 +56,7 @@ interface Message { // todo do we want to have test for this? fun streamUpdates(callback: (message: T) -> Unit): AutoCloseable + fun restore(): PNFuture + 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 18f44774..ce52d727 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 @@ -8,6 +8,7 @@ import com.pubnub.api.models.consumer.PNBoundedPage import com.pubnub.api.models.consumer.PNPublishResult import com.pubnub.api.models.consumer.history.PNFetchMessageItem import com.pubnub.api.models.consumer.history.PNFetchMessagesResult +import com.pubnub.api.models.consumer.message_actions.PNMessageAction import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult import com.pubnub.api.models.consumer.objects.PNKey import com.pubnub.api.models.consumer.objects.PNMembershipKey @@ -70,6 +71,7 @@ import com.pubnub.chat.internal.error.PubNubErrorMessage.THERE_IS_NO_ACTION_TIME import com.pubnub.chat.internal.error.PubNubErrorMessage.THERE_IS_NO_THREAD_TO_BE_DELETED import com.pubnub.chat.internal.error.PubNubErrorMessage.THERE_IS_NO_THREAD_WITH_ID import com.pubnub.chat.internal.error.PubNubErrorMessage.THIS_MESSAGE_IS_NOT_A_THREAD +import com.pubnub.chat.internal.error.PubNubErrorMessage.THIS_THREAD_ID_ALREADY_RESTORED import com.pubnub.chat.internal.error.PubNubErrorMessage.THREAD_FOR_THIS_MESSAGE_ALREADY_EXISTS import com.pubnub.chat.internal.error.PubNubErrorMessage.USER_ID_ALREADY_EXIST import com.pubnub.chat.internal.error.PubNubErrorMessage.USER_NOT_EXIST @@ -178,6 +180,28 @@ class ChatImpl( type = user.type ) + override fun restoreThreadChannel(message: Message): PNFuture { + val threadChannelId = getThreadId(message.channelId, message.timetoken) + return getChannel(threadChannelId).thenAsync { channel: Channel? -> + if (channel == null) { + null.asFuture() + } else { + message.actions?.get(THREAD_ROOT_ID)?.get(threadChannelId) + ?.get(0)?.actionTimetoken?.let { nonNullActionTimetoken -> + log.pnError(THIS_THREAD_ID_ALREADY_RESTORED) + } ?: run { + val messageAction = PNMessageAction( + type = THREAD_ROOT_ID, + value = threadChannelId, + messageTimetoken = message.timetoken + ) + pubNub.addMessageAction(channel = message.channelId, messageAction = messageAction) + // we don't update action map here but we do this in message#restore() + } + } + } + } + override fun createUser( id: String, name: String?, @@ -201,6 +225,34 @@ class ChatImpl( } } + override fun removeThreadChannel( + chat: Chat, + message: Message, + soft: Boolean + ): PNFuture> { + if (!message.hasThread) { + return PubNubException(THERE_IS_NO_THREAD_TO_BE_DELETED).logErrorAndReturnException(log).asFuture() + } + + val threadId = getThreadId(message.channelId, message.timetoken) + + val actionTimetoken = + message.actions?.get(THREAD_ROOT_ID)?.get(threadId)?.get(0)?.actionTimetoken + ?: return PubNubException(THERE_IS_NO_ACTION_TIMETOKEN_CORRESPONDING_TO_THE_THREAD).logErrorAndReturnException( + log + ).asFuture() + + return chat.getChannel(threadId).thenAsync { threadChannel -> + if (threadChannel == null) { + log.pnError("$THERE_IS_NO_THREAD_WITH_ID$threadId") + } + awaitAll( + chat.pubNub.removeMessageAction(message.channelId, message.timetoken, actionTimetoken), + threadChannel.delete(soft) + ) + } + } + override fun getUser(userId: String): PNFuture { if (!isValidId(userId)) { return log.logErrorAndReturnException(ID_IS_REQUIRED).asFuture() @@ -1118,34 +1170,6 @@ class ChatImpl( ).asFuture() } } - - internal fun removeThreadChannel( - chat: Chat, - message: Message, - soft: Boolean = false - ): PNFuture> { - if (!message.hasThread) { - return PubNubException(THERE_IS_NO_THREAD_TO_BE_DELETED).logErrorAndReturnException(log).asFuture() - } - - val threadId = getThreadId(message.channelId, message.timetoken) - - val actionTimetoken = - message.actions?.get("threadRootId")?.get(threadId)?.get(0)?.actionTimetoken - ?: return PubNubException(THERE_IS_NO_ACTION_TIMETOKEN_CORRESPONDING_TO_THE_THREAD).logErrorAndReturnException( - log - ).asFuture() - - return chat.getChannel(threadId).thenAsync { threadChannel -> - if (threadChannel == null) { - log.pnError("$THERE_IS_NO_THREAD_WITH_ID$threadId") - } - awaitAll( - chat.pubNub.removeMessageAction(message.channelId, message.timetoken, actionTimetoken), - threadChannel.delete(soft) - ) - } - } } private fun storeUserActivityTimestamp(): PNFuture { diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatInternal.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatInternal.kt index b10e47ec..976cb692 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatInternal.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatInternal.kt @@ -1,6 +1,10 @@ package com.pubnub.chat.internal +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult +import com.pubnub.chat.Channel import com.pubnub.chat.Chat +import com.pubnub.chat.Message import com.pubnub.chat.User import com.pubnub.kmp.PNFuture @@ -9,4 +13,12 @@ interface ChatInternal : Chat { val deleteMessageActionName: String fun createUser(user: User): PNFuture + + fun removeThreadChannel( + chat: Chat, + message: Message, + soft: Boolean = false + ): PNFuture> + + fun restoreThreadChannel(message: Message): PNFuture } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt index b2f6f4d0..51de5b25 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt @@ -770,6 +770,7 @@ abstract class BaseChannel( val newChannel = previousChannel?.plus(message.data) ?: ChannelImpl.fromDTO(chat, message.data) newChannel to newChannelId } + is PNDeleteChannelMetadataEventMessage -> null to message.channel else -> return@createEventListener } 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 7d32914c..c249aeb8 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 @@ -2,7 +2,10 @@ package com.pubnub.chat.internal.channel import com.pubnub.api.models.consumer.PNPublishResult import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata +import com.pubnub.api.v2.callbacks.Result +import com.pubnub.api.v2.callbacks.map import com.pubnub.chat.Channel import com.pubnub.chat.Message import com.pubnub.chat.ThreadChannel @@ -10,6 +13,7 @@ import com.pubnub.chat.ThreadMessage import com.pubnub.chat.internal.ChatImpl import com.pubnub.chat.internal.ChatInternal import com.pubnub.chat.internal.DELETED +import com.pubnub.chat.internal.THREAD_ROOT_ID import com.pubnub.chat.internal.error.PubNubErrorMessage.PARENT_CHANNEL_DOES_NOT_EXISTS import com.pubnub.chat.internal.message.ThreadMessageImpl import com.pubnub.chat.internal.util.pnError @@ -74,6 +78,19 @@ data class ThreadChannelImpl( } } + override fun delete(soft: Boolean): PNFuture { + val removeThreadChannel = chat.removeThreadChannel(chat, parentMessage, soft) + return PNFuture { callback -> + removeThreadChannel.async { result: Result> -> + result.map { pair: Pair -> + pair.second + }.let { channelResult: Result -> + callback.accept(channelResult) + } + } + } + } + override fun sendText( text: String, meta: Map?, @@ -93,7 +110,7 @@ data class ThreadChannelImpl( chat.pubNub.addMessageAction( parentMessage.channelId, PNMessageAction( - "threadRootId", + THREAD_ROOT_ID, id, parentMessage.timetoken ) diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt index 1e916723..f69e7351 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt @@ -70,4 +70,6 @@ internal object PubNubErrorMessage { internal const val THREAD_FOR_THIS_MESSAGE_ALREADY_EXISTS = "Thread for this message already exists." internal const val RECEIPT_EVENT_WAS_NOT_SENT_TO_CHANNEL = "Because PAM did not allow it 'receipt' event was not sent to channel: " internal const val ERROR_HANDLING_ONMESSAGE_EVENT = "Error handling onMessage event" + internal const val THIS_MESSAGE_HAS_NOT_BEEN_DELETED = "This message has not been deleted" + internal const val THIS_THREAD_ID_ALREADY_RESTORED = "This thread is already restored" } 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 8b94f60d..91465137 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 @@ -1,10 +1,14 @@ package com.pubnub.chat.internal.message import com.pubnub.api.JsonElement +import com.pubnub.api.PubNubException import com.pubnub.api.asMap +import com.pubnub.api.endpoints.message_actions.RemoveMessageAction +import com.pubnub.api.models.consumer.PNBoundedPage import com.pubnub.api.models.consumer.PNPublishResult import com.pubnub.api.models.consumer.history.PNFetchMessageItem import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult +import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult import com.pubnub.api.models.consumer.message_actions.PNMessageAction import com.pubnub.chat.Channel import com.pubnub.chat.Message @@ -20,7 +24,9 @@ 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.error.PubNubErrorMessage.THIS_MESSAGE_HAS_NOT_BEEN_DELETED import com.pubnub.chat.internal.serialization.PNDataEncoder +import com.pubnub.chat.internal.util.logWarnAndReturnException import com.pubnub.chat.internal.util.pnError import com.pubnub.chat.types.EventContent import com.pubnub.chat.types.File @@ -68,7 +74,7 @@ abstract class BaseMessage( } override val deleted: Boolean - get() = actions?.get(chat.deleteMessageActionName)?.get(chat.deleteMessageActionName)?.isNotEmpty() ?: false + get() = getDeleteAction() != null override val hasThread: Boolean get() { @@ -84,7 +90,8 @@ abstract class BaseMessage( override val files: List get() = content.files ?: emptyList() - override val reactions get() = actions?.get(com.pubnub.chat.types.MessageActionType.REACTIONS.toString()) ?: emptyMap() + override val reactions + get() = actions?.get(com.pubnub.chat.types.MessageActionType.REACTIONS.toString()) ?: emptyMap() override val textLinks: List? get() = ( meta?.get( @@ -188,13 +195,14 @@ abstract class BaseMessage( override fun createThread(): PNFuture = ChatImpl.createThreadChannel(chat, this) - override fun removeThread() = ChatImpl.removeThreadChannel(chat, this) + override fun removeThread() = chat.removeThreadChannel(chat, this) override fun toggleReaction(reaction: String): PNFuture { val existingReaction = reactions[reaction]?.find { it.uuid == chat.currentUser.id } - val messageAction = PNMessageAction(com.pubnub.chat.types.MessageActionType.REACTIONS.toString(), reaction, timetoken) + val messageAction = + PNMessageAction(com.pubnub.chat.types.MessageActionType.REACTIONS.toString(), reaction, timetoken) val newActions = if (existingReaction != null) { chat.pubNub.removeMessageAction(channelId, timetoken, existingReaction.actionTimetoken.toLong()) .then { filterAction(actions, messageAction) } @@ -205,7 +213,61 @@ abstract class BaseMessage( return newActions.then { copyWithActions(it) } } + override fun streamUpdates(callback: (message: M) -> Unit): AutoCloseable { + return streamUpdatesOn(listOf(this as M)) { + callback(it.first()) + } + } + + override fun restore(): PNFuture { + if (!deleted) { + return PubNubException(THIS_MESSAGE_HAS_NOT_BEEN_DELETED).logWarnAndReturnException(log).asFuture() + } + + var updatedActions: Map>> = mapOf() + val deleteAction: PNFetchMessageItem.Action = getDeleteAction()!! + val removeMessageAction = removeMessageAction(deleteAction.actionTimetoken) + + return removeMessageAction.then { + // from actions map remove entries associated with delete operations + updatedActions = actions!!.filterNot { it.key == chat.deleteMessageActionName } + }.thenAsync { + // get messageAction for all messages in channel + chat.pubNub.getMessageActions(channel = channelId, page = PNBoundedPage(end = timetoken)) + }.then { pnGetMessageActionsResult: PNGetMessageActionsResult -> + // getMessageAction assigned to this message + val messageActionsForMessage = pnGetMessageActionsResult.actions.filter { it.messageTimetoken == timetoken } + + // update actions map + messageActionsForMessage.forEach { pnMessageAction -> + updatedActions = assignAction(updatedActions, pnMessageAction) + } + updatedActions + }.thenAsync { + chat.restoreThreadChannel(this) + }.then { pnMessageAction: PNMessageAction? -> + // update actions map + pnMessageAction?.let { updatedActions = assignAction(updatedActions, pnMessageAction) } + }.then { + copyWithActions(updatedActions) + } + } + + private fun removeMessageAction(deleteActionTimetoken: Long): RemoveMessageAction { + val removeMessageAction = chat.pubNub.removeMessageAction( + channel = channelId, + messageTimetoken = timetoken, + actionTimetoken = deleteActionTimetoken + ) + return removeMessageAction + } + + private fun getDeleteAction(): PNFetchMessageItem.Action? { + return actions?.get(chat.deleteMessageActionName)?.get(chat.deleteMessageActionName)?.first() + } + private fun deleteThread(soft: Boolean): PNFuture { + // todo check on server, discuss with Team if (hasThread) { return getThread().thenAsync { it.delete(soft) @@ -224,12 +286,6 @@ abstract class BaseMessage( internal abstract fun copyWithActions(actions: Actions): T - override fun streamUpdates(callback: (message: M) -> Unit): AutoCloseable { - return streamUpdatesOn(listOf(this as M)) { - callback(it.first()) - } - } - companion object { private val log = logging() @@ -244,7 +300,8 @@ abstract class BaseMessage( val chat = messages.first().chat val listener = createEventListener(chat.pubNub, onMessageAction = { _, event -> val message = - latestMessages.find { it.timetoken == event.messageAction.messageTimetoken } ?: return@createEventListener + latestMessages.find { it.timetoken == event.messageAction.messageTimetoken } + ?: return@createEventListener if (message.channelId != event.channel) { return@createEventListener } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/util/Utils.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/util/Utils.kt index 89bb96b4..13a3bf64 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/util/Utils.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/util/Utils.kt @@ -31,10 +31,14 @@ internal val PNFetchMessagesResult.channelsUrlDecoded: Map>> = + mapOf("deleted" to mapOf("deleted" to listOf(Action("user1", 1234L)))) + objectUnderTest = MessageImpl( + chat = chat, + timetoken = timetoken, + content = messageContent, + channelId = channelId, + userId = userId, + actions = actionsWithEntryIndicatingThatMessageHasBeenDeleted + ) + every { chat.deleteMessageActionName } returns "DELETED" + + objectUnderTest.restore().async { result: Result -> + assertTrue(result.isFailure) + assertEquals("This message has not been deleted", result.exceptionOrNull()?.message) + println(result) + } + } +} \ No newline at end of file diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/utils/FakeChat.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/utils/FakeChat.kt index 5a1aecf8..2ff1820b 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/utils/FakeChat.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/utils/FakeChat.kt @@ -2,6 +2,8 @@ package com.pubnub.kmp.utils import com.pubnub.api.PubNub import com.pubnub.api.models.consumer.PNPublishResult +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult import com.pubnub.api.models.consumer.objects.PNKey import com.pubnub.api.models.consumer.objects.PNMembershipKey import com.pubnub.api.models.consumer.objects.PNPage @@ -9,6 +11,7 @@ import com.pubnub.api.models.consumer.objects.PNSortKey import com.pubnub.api.models.consumer.push.PNPushAddChannelResult import com.pubnub.api.models.consumer.push.PNPushRemoveChannelResult import com.pubnub.chat.Channel +import com.pubnub.chat.Chat import com.pubnub.chat.Event import com.pubnub.chat.Message import com.pubnub.chat.ThreadChannel @@ -43,6 +46,14 @@ abstract class FakeChat(override val config: ChatConfiguration, override val pub TODO("Not yet implemented") } + override fun removeThreadChannel( + chat: Chat, + message: Message, + soft: Boolean + ): PNFuture> { + TODO("Not yet implemented") + } + override fun getCurrentUserMentions( startTimetoken: Long?, endTimetoken: Long?, @@ -258,4 +269,8 @@ abstract class FakeChat(override val config: ChatConfiguration, override val pub override fun unregisterAllPushChannels(): PNFuture { TODO("Not yet implemented") } + + override fun restoreThreadChannel(message: Message): PNFuture { + TODO("Not yet implemented") + } } diff --git a/src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt b/src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt index 673e9265..35ef52c6 100644 --- a/src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt +++ b/src/jvmTest/kotlin/compubnub/chat/ChatIntegrationTest.kt @@ -11,10 +11,9 @@ import com.pubnub.chat.init import kotlin.test.Test class ChatIntegrationTest { - @Test fun canInitializeChatWithLogLevel() { - val chatConfig = ChatConfiguration(logLevel = LogLevel.INFO) + val chatConfig = ChatConfiguration(logLevel = LogLevel.OFF) val pnConfiguration = PNConfiguration.builder(userId = UserId("myUserId"), subscribeKey = "mySubscribeKey").build() Chat.init(chatConfig, pnConfiguration).async { result: Result -> @@ -24,6 +23,5 @@ class ChatIntegrationTest { println("Exception initialising chat: ${exception.message}") } } - } } From f39c3151b75e0414ae38dde2d0ad0ef48fb8c3ab Mon Sep 17 00:00:00 2001 From: "marcin.cebo" Date: Fri, 16 Aug 2024 17:33:24 +0200 Subject: [PATCH 3/4] Fix lint --- .../src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt | 2 +- .../src/commonTest/kotlin/com/pubnub/kmp/MessageTest.kt | 2 +- pubnub-kotlin | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt index 3300e27c..bab88bf5 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt @@ -197,7 +197,7 @@ class ChatTest : BaseTest() { } @Test - fun whenCreatingUseriWithcanCreateUser() { //todo + fun whenCreatingUseriWithcanCreateUser() { // todo } @Test diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MessageTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MessageTest.kt index aeb32c6f..df5a8daf 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MessageTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MessageTest.kt @@ -57,4 +57,4 @@ class MessageTest { println(result) } } -} \ No newline at end of file +} diff --git a/pubnub-kotlin b/pubnub-kotlin index e47ee8d7..22a7566d 160000 --- a/pubnub-kotlin +++ b/pubnub-kotlin @@ -1 +1 @@ -Subproject commit e47ee8d7a3121238755b6e4f2baaa64463c6b606 +Subproject commit 22a7566d8d2faa335c377fbc8ac5422242a1f681 From 0b9f17601080c6b7d966a6356fae6814e30de84f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojtek=20Kalici=C5=84ski?= <146713236+wkal-pubnub@users.noreply.github.com> Date: Tue, 3 Sep 2024 07:36:56 +0000 Subject: [PATCH 4/4] PR review / proposed fixes (#62) --- .../com/pubnub/chat/internal/ChatImpl.kt | 21 +++--- .../internal/channel/ThreadChannelImpl.kt | 14 +--- .../chat/internal/message/BaseMessage.kt | 73 +++++++++---------- .../chat/internal/message/MessageImpl.kt | 2 +- .../internal/message/ThreadMessageImpl.kt | 2 +- 5 files changed, 48 insertions(+), 64 deletions(-) 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 ce52d727..73c3a353 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 @@ -186,18 +186,17 @@ class ChatImpl( if (channel == null) { null.asFuture() } else { - message.actions?.get(THREAD_ROOT_ID)?.get(threadChannelId) - ?.get(0)?.actionTimetoken?.let { nonNullActionTimetoken -> - log.pnError(THIS_THREAD_ID_ALREADY_RESTORED) - } ?: run { - val messageAction = PNMessageAction( - type = THREAD_ROOT_ID, - value = threadChannelId, - messageTimetoken = message.timetoken - ) - pubNub.addMessageAction(channel = message.channelId, messageAction = messageAction) - // we don't update action map here but we do this in message#restore() + if (message.actions?.get(THREAD_ROOT_ID)?.get(threadChannelId)?.isNotEmpty() == true) { + log.pnError(THIS_THREAD_ID_ALREADY_RESTORED) } + + val messageAction = PNMessageAction( + type = THREAD_ROOT_ID, + value = threadChannelId, + messageTimetoken = message.timetoken + ) + pubNub.addMessageAction(channel = message.channelId, messageAction = messageAction) + // we don't update action map here but we do this in message#restore() } } } 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 c249aeb8..45196f77 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 @@ -2,10 +2,7 @@ package com.pubnub.chat.internal.channel import com.pubnub.api.models.consumer.PNPublishResult import com.pubnub.api.models.consumer.message_actions.PNMessageAction -import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata -import com.pubnub.api.v2.callbacks.Result -import com.pubnub.api.v2.callbacks.map import com.pubnub.chat.Channel import com.pubnub.chat.Message import com.pubnub.chat.ThreadChannel @@ -79,16 +76,7 @@ data class ThreadChannelImpl( } override fun delete(soft: Boolean): PNFuture { - val removeThreadChannel = chat.removeThreadChannel(chat, parentMessage, soft) - return PNFuture { callback -> - removeThreadChannel.async { result: Result> -> - result.map { pair: Pair -> - pair.second - }.let { channelResult: Result -> - callback.accept(channelResult) - } - } - } + return chat.removeThreadChannel(chat, parentMessage, soft).then { it.second } } override fun sendText( 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 91465137..025a4cc0 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 @@ -30,6 +30,7 @@ import com.pubnub.chat.internal.util.logWarnAndReturnException import com.pubnub.chat.internal.util.pnError import com.pubnub.chat.types.EventContent import com.pubnub.chat.types.File +import com.pubnub.chat.types.MessageActionType import com.pubnub.chat.types.MessageMentionedUsers import com.pubnub.chat.types.MessageReferencedChannels import com.pubnub.chat.types.QuotedMessage @@ -62,7 +63,7 @@ abstract class BaseMessage( override val text: String get() { val edits = actions?.get(chat.editMessageActionName) ?: return content.text - val flatEdits = edits.mapValues { it.value.first() } + val flatEdits = edits.filterValues { it.isNotEmpty() }.mapValues { it.value.first() } val lastEdit = flatEdits.entries.reduce { acc, entry -> if (acc.value.actionTimetoken > entry.value.actionTimetoken) { acc @@ -74,7 +75,7 @@ abstract class BaseMessage( } override val deleted: Boolean - get() = getDeleteAction() != null + get() = getDeleteActions() != null override val hasThread: Boolean get() { @@ -90,8 +91,8 @@ abstract class BaseMessage( override val files: List get() = content.files ?: emptyList() - override val reactions - get() = actions?.get(com.pubnub.chat.types.MessageActionType.REACTIONS.toString()) ?: emptyMap() + override val reactions: Map> + get() = actions?.get(MessageActionType.REACTIONS.toString()) ?: emptyMap() override val textLinks: List? get() = ( meta?.get( @@ -202,7 +203,7 @@ abstract class BaseMessage( it.uuid == chat.currentUser.id } val messageAction = - PNMessageAction(com.pubnub.chat.types.MessageActionType.REACTIONS.toString(), reaction, timetoken) + PNMessageAction(MessageActionType.REACTIONS.toString(), reaction, timetoken) val newActions = if (existingReaction != null) { chat.pubNub.removeMessageAction(channelId, timetoken, existingReaction.actionTimetoken.toLong()) .then { filterAction(actions, messageAction) } @@ -220,50 +221,46 @@ abstract class BaseMessage( } override fun restore(): PNFuture { - if (!deleted) { - return PubNubException(THIS_MESSAGE_HAS_NOT_BEEN_DELETED).logWarnAndReturnException(log).asFuture() + val deleteActions: List = getDeleteActions() + ?: return PubNubException(THIS_MESSAGE_HAS_NOT_BEEN_DELETED).logWarnAndReturnException(log).asFuture() + + var updatedActions: Actions? = actions?.filterNot { + it.key == chat.deleteMessageActionName } - var updatedActions: Map>> = mapOf() - val deleteAction: PNFetchMessageItem.Action = getDeleteAction()!! - val removeMessageAction = removeMessageAction(deleteAction.actionTimetoken) - - return removeMessageAction.then { - // from actions map remove entries associated with delete operations - updatedActions = actions!!.filterNot { it.key == chat.deleteMessageActionName } - }.thenAsync { - // get messageAction for all messages in channel - chat.pubNub.getMessageActions(channel = channelId, page = PNBoundedPage(end = timetoken)) - }.then { pnGetMessageActionsResult: PNGetMessageActionsResult -> - // getMessageAction assigned to this message - val messageActionsForMessage = pnGetMessageActionsResult.actions.filter { it.messageTimetoken == timetoken } - - // update actions map - messageActionsForMessage.forEach { pnMessageAction -> - updatedActions = assignAction(updatedActions, pnMessageAction) + return deleteActions + .map { removeMessageAction(it.actionTimetoken) } + .awaitAll() + .thenAsync { + // get messageAction for all messages in channel + chat.pubNub.getMessageActions(channel = channelId, page = PNBoundedPage(end = timetoken)) + }.then { pnGetMessageActionsResult: PNGetMessageActionsResult -> + // getMessageAction assigned to this message + val messageActionsForMessage = pnGetMessageActionsResult.actions.filter { it.messageTimetoken == timetoken } + + // update actions map + messageActionsForMessage.forEach { pnMessageAction -> + updatedActions = assignAction(updatedActions, pnMessageAction) + } + }.thenAsync { + chat.restoreThreadChannel(this) + }.then { pnMessageAction: PNMessageAction? -> + // update actions map + pnMessageAction?.let { updatedActions = assignAction(updatedActions, it) } + copyWithActions(updatedActions) } - updatedActions - }.thenAsync { - chat.restoreThreadChannel(this) - }.then { pnMessageAction: PNMessageAction? -> - // update actions map - pnMessageAction?.let { updatedActions = assignAction(updatedActions, pnMessageAction) } - }.then { - copyWithActions(updatedActions) - } } private fun removeMessageAction(deleteActionTimetoken: Long): RemoveMessageAction { - val removeMessageAction = chat.pubNub.removeMessageAction( + return chat.pubNub.removeMessageAction( channel = channelId, messageTimetoken = timetoken, actionTimetoken = deleteActionTimetoken ) - return removeMessageAction } - private fun getDeleteAction(): PNFetchMessageItem.Action? { - return actions?.get(chat.deleteMessageActionName)?.get(chat.deleteMessageActionName)?.first() + private fun getDeleteActions(): List? { + return actions?.get(chat.deleteMessageActionName)?.get(chat.deleteMessageActionName) } private fun deleteThread(soft: Boolean): PNFuture { @@ -284,7 +281,7 @@ abstract class BaseMessage( ) } - internal abstract fun copyWithActions(actions: Actions): T + internal abstract fun copyWithActions(actions: Actions?): T companion object { private val log = logging() diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/MessageImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/MessageImpl.kt index ad059309..0d76ee23 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/MessageImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/MessageImpl.kt @@ -35,7 +35,7 @@ data class MessageImpl( referencedChannels = referencedChannels, quotedMessage = quotedMessage ) { - override fun copyWithActions(actions: Actions): Message = copy(actions = actions) + override fun copyWithActions(actions: Actions?): Message = copy(actions = actions) companion object { internal fun fromDTO(chat: ChatInternal, pnMessageResult: PNMessageResult): Message { 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 2506f26e..3a94ec89 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 @@ -47,7 +47,7 @@ data class ThreadMessageImpl( quotedMessage = quotedMessage ), ThreadMessage { - override fun copyWithActions(actions: Actions): ThreadMessage = copy(actions = actions) + override fun copyWithActions(actions: Actions?): ThreadMessage = copy(actions = actions) companion object { private val log = logging()