From c251f5e2bd9f691032cd23f1b840f3558a9f60cf Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 1 Jun 2024 00:54:12 +0700 Subject: [PATCH] android, desktop: debug subscribed SMP queues (#4262) Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/SimpleXAPI.kt | 72 +++++++++++++++++++ .../simplex/common/views/chat/ChatInfoView.kt | 20 ++++++ .../views/chat/group/GroupMemberInfoView.kt | 18 +++++ .../commonMain/resources/MR/base/strings.xml | 4 ++ 4 files changed, 114 insertions(+) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index b0557e2f67..1f7f55fb53 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -931,6 +931,20 @@ object ChatController { return null } + suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { + val r = sendCmd(rh, CC.APIContactQueueInfo(contactId)) + if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) + apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) + return null + } + + suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) + if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) + apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) + return null + } + suspend fun apiSwitchContact(rh: Long?, contactId: Long): ConnectionStats? { val r = sendCmd(rh, CC.APISwitchContact(contactId)) if (r is CR.ContactSwitchStarted) return r.connectionStats @@ -2507,6 +2521,8 @@ sealed class CC { class ApiSetMemberSettings(val groupId: Long, val groupMemberId: Long, val memberSettings: GroupMemberSettings): CC() class APIContactInfo(val contactId: Long): CC() class APIGroupMemberInfo(val groupId: Long, val groupMemberId: Long): CC() + class APIContactQueueInfo(val contactId: Long): CC() + class APIGroupMemberQueueInfo(val groupId: Long, val groupMemberId: Long): CC() class APISwitchContact(val contactId: Long): CC() class APISwitchGroupMember(val groupId: Long, val groupMemberId: Long): CC() class APIAbortSwitchContact(val contactId: Long): CC() @@ -2652,6 +2668,8 @@ sealed class CC { is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}" is APIContactInfo -> "/_info @$contactId" is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId" + is APIContactQueueInfo -> "/_queue info @$contactId" + is APIGroupMemberQueueInfo -> "/_queue info #$groupId $groupMemberId" is APISwitchContact -> "/_switch @$contactId" is APISwitchGroupMember -> "/_switch #$groupId $groupMemberId" is APIAbortSwitchContact -> "/_abort switch @$contactId" @@ -2790,6 +2808,8 @@ sealed class CC { is ApiSetMemberSettings -> "apiSetMemberSettings" is APIContactInfo -> "apiContactInfo" is APIGroupMemberInfo -> "apiGroupMemberInfo" + is APIContactQueueInfo -> "apiContactQueueInfo" + is APIGroupMemberQueueInfo -> "apiGroupMemberQueueInfo" is APISwitchContact -> "apiSwitchContact" is APISwitchGroupMember -> "apiSwitchGroupMember" is APIAbortSwitchContact -> "apiAbortSwitchContact" @@ -4197,6 +4217,7 @@ sealed class CR { @Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR() @Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR() @Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats? = null): CR() + @Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: QueueInfo): CR() @Serializable @SerialName("contactSwitchStarted") class ContactSwitchStarted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("groupMemberSwitchStarted") class GroupMemberSwitchStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("contactSwitchAborted") class ContactSwitchAborted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @@ -4368,6 +4389,7 @@ sealed class CR { is NetworkConfig -> "networkConfig" is ContactInfo -> "contactInfo" is GroupMemberInfo -> "groupMemberInfo" + is QueueInfoR -> "queueInfo" is ContactSwitchStarted -> "contactSwitchStarted" is GroupMemberSwitchStarted -> "groupMemberSwitchStarted" is ContactSwitchAborted -> "contactSwitchAborted" @@ -4529,6 +4551,7 @@ sealed class CR { is NetworkConfig -> json.encodeToString(networkConfig) is ContactInfo -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats_)}") is GroupMemberInfo -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats_)}") + is QueueInfoR -> withUser(user, "rcvMsgInfo: ${json.encodeToString(rcvMsgInfo)}\nqueueInfo: ${json.encodeToString(queueInfo)}\n") is ContactSwitchStarted -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}") is GroupMemberSwitchStarted -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats)}") is ContactSwitchAborted -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}") @@ -5786,3 +5809,52 @@ enum class UserNetworkType { OTHER -> generalGetString(MR.strings.network_type_other) } } + +@Serializable +data class RcvMsgInfo ( + val msgId: Long, + val msgDeliveryId: Long, + val msgDeliveryStatus: String, + val agentMsgId: Long, + val agentMsgMeta: String +) + +@Serializable +data class QueueInfo ( + val qiSnd: Boolean, + val qiNtf: Boolean, + val qiSub: QSub? = null, + val qiSize: Int, + val qiMsg: MsgInfo? = null +) + +@Serializable +data class QSub ( + val qSubThread: QSubThread, + val qDelivered: String? = null +) + +enum class QSubThread { + @SerialName("noSub") + NO_SUB, + @SerialName("subPending") + SUB_PENDING, + @SerialName("subThread") + SUB_THREAD, + @SerialName("prohibitSub") + PROHIBIT_SUB +} + +@Serializable +data class MsgInfo ( + val msgId: String, + val msgTs: Instant, + val msgType: MsgType, +) + +enum class MsgType { + @SerialName("message") + MESSAGE, + @SerialName("quota") + QUOTA +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 12b5747787..48ed0570a7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.datetime.Clock +import kotlinx.serialization.encodeToString import java.io.File @Composable @@ -418,6 +419,19 @@ fun ChatInfoLayout( SectionView(title = stringResource(MR.strings.section_title_for_console)) { InfoRow(stringResource(MR.strings.info_row_local_name), chat.chatInfo.localDisplayName) InfoRow(stringResource(MR.strings.info_row_database_id), chat.chatInfo.apiId.toString()) + SectionItemView({ + withBGApi { + val info = controller.apiContactQueueInfo(chat.remoteHostId, chat.chatInfo.apiId) + if (info != null) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.message_queue_info), + text = queueInfoText(info) + ) + } + } + }) { + Text(stringResource(MR.strings.info_row_debug_delivery)) + } } } SectionBottomSpacer() @@ -798,6 +812,12 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) { ) } +fun queueInfoText(info: Pair): String { + val (rcvMsgInfo, qInfo) = info + val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none) + return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo) +} + @Preview @Composable fun PreviewChatInfoLayout() { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index e90efa7d1b..2799904b71 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -3,6 +3,7 @@ package chat.simplex.common.views.chat.group import InfoRow import SectionBottomSpacer import SectionDividerSpaced +import SectionItemView import SectionSpacer import SectionTextFooter import SectionView @@ -27,6 +28,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.helpers.* @@ -58,6 +60,7 @@ fun GroupMemberInfoView( if (chat != null) { val newRole = remember { mutableStateOf(member.memberRole) } GroupMemberInfoLayout( + rhId = rhId, groupInfo, member, connStats, @@ -219,6 +222,7 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c @Composable fun GroupMemberInfoLayout( + rhId: Long?, groupInfo: GroupInfo, member: GroupMember, connStats: MutableState, @@ -397,6 +401,19 @@ fun GroupMemberInfoLayout( SectionView(title = stringResource(MR.strings.section_title_for_console)) { InfoRow(stringResource(MR.strings.info_row_local_name), member.localDisplayName) InfoRow(stringResource(MR.strings.info_row_database_id), member.groupMemberId.toString()) + SectionItemView({ + withBGApi { + val info = controller.apiGroupMemberQueueInfo(rhId, groupInfo.apiId, member.groupMemberId) + if (info != null) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.message_queue_info), + text = queueInfoText(info) + ) + } + } + }) { + Text(stringResource(MR.strings.info_row_debug_delivery)) + } } } SectionBottomSpacer() @@ -644,6 +661,7 @@ fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, member: GroupMember, blocke fun PreviewGroupMemberInfoLayout() { SimpleXTheme { GroupMemberInfoLayout( + rhId = null, groupInfo = GroupInfo.sampleData, member = GroupMember.sampleData, connStats = remember { mutableStateOf(null) }, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index d930a1b72b..df43d1459f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1401,6 +1401,7 @@ FOR CONSOLE Local name Database ID + Debug delivery Record updated at Sent at Created at @@ -1462,6 +1463,9 @@ Connection direct indirect (%1$s) + Message queue info + none + server queue info: %1$s\n\nlast received msg: %2$s Welcome message