core: commands to debug subscribed SMP queues (#4253)

* core: commands to debug subscribed SMP queues

* getLastRcvMsgInfo

* DESC

* update

* update commands, ui compiles

* update

* ios: fix type

* swap

* simplexmq

* group member queue info

* copy button in the alert
This commit is contained in:
Evgeny Poberezkin
2024-05-31 12:45:58 +01:00
committed by GitHub
parent 7b5b747b19
commit 5ac521f6d8
12 changed files with 210 additions and 42 deletions
+12
View File
@@ -561,6 +561,18 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (Gro
throw r
}
func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) {
let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId))
if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) }
throw r
}
func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) {
let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId))
if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) }
throw r
}
func apiSwitchContact(contactId: Int64) throws -> ConnectionStats {
let r = chatSendCmdSync(.apiSwitchContact(contactId: contactId))
if case let .contactSwitchStarted(_, _, connectionStats) = r { return connectionStats }
@@ -110,6 +110,7 @@ struct ChatInfoView: View {
case switchAddressAlert
case abortSwitchAddressAlert
case syncConnectionForceAlert
case queueInfo(info: String)
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
var id: String {
@@ -119,6 +120,7 @@ struct ChatInfoView: View {
case .switchAddressAlert: return "switchAddressAlert"
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case let .queueInfo(info): return "queueInfo \(info)"
case let .error(title, _): return "error \(title)"
}
}
@@ -224,6 +226,18 @@ struct ChatInfoView: View {
Section(header: Text("For console")) {
infoRow("Local name", chat.chatInfo.localDisplayName)
infoRow("Database ID", "\(chat.chatInfo.apiId)")
Button ("Debug delivery") {
Task {
do {
let info = queueInfoText(try await apiContactQueueInfo(chat.chatInfo.apiId))
await MainActor.run { alert = .queueInfo(info: info) }
} catch let e {
logger.error("apiContactQueueInfo error: \(responseError(e))")
let a = getErrorAlert(e, "Error")
await MainActor.run { alert = .error(title: a.title, error: a.message) }
}
}
}
}
}
}
@@ -243,6 +257,7 @@ struct ChatInfoView: View {
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) })
case let .queueInfo(info): return queueInfoAlert(info)
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
@@ -577,6 +592,22 @@ func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Al
)
}
func queueInfoText(_ info: (RcvMsgInfo?, QueueInfo)) -> String {
let (rcvMsgInfo, qInfo) = info
var msgInfo: String
if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" }
return String.localizedStringWithFormat(NSLocalizedString("server queue info: %@\n\nlast received msg: %@", comment: "queue info"), encodeJSON(qInfo), msgInfo)
}
func queueInfoAlert(_ info: String) -> Alert {
Alert(
title: Text("Message queue info"),
message: Text(info),
primaryButton: .default(Text("Ok")),
secondaryButton: .default(Text("Copy")) { UIPasteboard.general.string = info }
)
}
struct ChatInfoView_Previews: PreviewProvider {
static var previews: some View {
ChatInfoView(
@@ -35,6 +35,7 @@ struct GroupMemberInfoView: View {
case abortSwitchAddressAlert
case syncConnectionForceAlert
case planAndConnectAlert(alert: PlanAndConnectAlert)
case queueInfo(info: String)
case error(title: LocalizedStringKey, error: LocalizedStringKey)
var id: String {
@@ -49,6 +50,7 @@ struct GroupMemberInfoView: View {
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
case let .queueInfo(info): return "queueInfo \(info)"
case let .error(title, _): return "error \(title)"
}
}
@@ -178,6 +180,18 @@ struct GroupMemberInfoView: View {
Section("For console") {
infoRow("Local name", member.localDisplayName)
infoRow("Database ID", "\(member.groupMemberId)")
Button ("Debug delivery") {
Task {
do {
let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId))
await MainActor.run { alert = .queueInfo(info: info) }
} catch let e {
logger.error("apiContactQueueInfo error: \(responseError(e))")
let a = getErrorAlert(e, "Error")
await MainActor.run { alert = .error(title: a.title, error: a.message) }
}
}
}
}
}
}
@@ -223,6 +237,7 @@ struct GroupMemberInfoView: View {
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) })
case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true)
case let .queueInfo(info): return queueInfoAlert(info)
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
}
}
+20 -20
View File
@@ -24,6 +24,11 @@
5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; };
5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; };
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
5C0EA1272C08ADAF00AD2E5E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1222C08ADAF00AD2E5E /* libgmp.a */; };
5C0EA1282C08ADAF00AD2E5E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1232C08ADAF00AD2E5E /* libffi.a */; };
5C0EA1292C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1242C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5-ghc9.6.3.a */; };
5C0EA12A2C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1252C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5.a */; };
5C0EA12B2C08ADAF00AD2E5E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1262C08ADAF00AD2E5E /* libgmpxx.a */; };
5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */; };
5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */; };
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
@@ -139,11 +144,6 @@
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
5CEE879E2C076B8400583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87992C076B8300583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7.a */; };
5CEE879F2C076B8400583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE879A2C076B8300583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7-ghc9.6.3.a */; };
5CEE87A02C076B8400583B8A /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE879B2C076B8400583B8A /* libffi.a */; };
5CEE87A12C076B8400583B8A /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE879C2C076B8400583B8A /* libgmp.a */; };
5CEE87A22C076B8400583B8A /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE879D2C076B8400583B8A /* libgmpxx.a */; };
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
@@ -273,6 +273,11 @@
5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; };
5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; };
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; };
5C0EA1222C08ADAF00AD2E5E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C0EA1232C08ADAF00AD2E5E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C0EA1242C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5-ghc9.6.3.a"; sourceTree = "<group>"; };
5C0EA1252C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5.a"; sourceTree = "<group>"; };
5C0EA1262C08ADAF00AD2E5E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionInfo.swift; sourceTree = "<group>"; };
5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMediaView.swift; sourceTree = "<group>"; };
5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = "<group>"; };
@@ -435,11 +440,6 @@
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
5CEE87992C076B8300583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7.a"; sourceTree = "<group>"; };
5CEE879A2C076B8300583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7-ghc9.6.3.a"; sourceTree = "<group>"; };
5CEE879B2C076B8400583B8A /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5CEE879C2C076B8400583B8A /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5CEE879D2C076B8400583B8A /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
@@ -529,13 +529,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5CEE879E2C076B8400583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5CEE879F2C076B8400583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7-ghc9.6.3.a in Frameworks */,
5CEE87A02C076B8400583B8A /* libffi.a in Frameworks */,
5CEE87A12C076B8400583B8A /* libgmp.a in Frameworks */,
5C0EA1272C08ADAF00AD2E5E /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5CEE87A22C076B8400583B8A /* libgmpxx.a in Frameworks */,
5C0EA12A2C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5.a in Frameworks */,
5C0EA1292C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5-ghc9.6.3.a in Frameworks */,
5C0EA1282C08ADAF00AD2E5E /* libffi.a in Frameworks */,
5C0EA12B2C08ADAF00AD2E5E /* libgmpxx.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -601,11 +601,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
5CEE879B2C076B8400583B8A /* libffi.a */,
5CEE879C2C076B8400583B8A /* libgmp.a */,
5CEE879D2C076B8400583B8A /* libgmpxx.a */,
5CEE879A2C076B8300583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7-ghc9.6.3.a */,
5CEE87992C076B8300583B8A /* libHSsimplex-chat-5.8.0.4-LLYprD9nQTR9mmjl4EdHW7.a */,
5C0EA1232C08ADAF00AD2E5E /* libffi.a */,
5C0EA1222C08ADAF00AD2E5E /* libgmp.a */,
5C0EA1262C08ADAF00AD2E5E /* libgmpxx.a */,
5C0EA1242C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5-ghc9.6.3.a */,
5C0EA1252C08ADAF00AD2E5E /* libHSsimplex-chat-5.8.0.4-2QdMx3VWkTS77pHWzbDqa5.a */,
);
path = Libraries;
sourceTree = "<group>";
+50
View File
@@ -82,6 +82,8 @@ public enum ChatCommand {
case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings)
case apiContactInfo(contactId: Int64)
case apiGroupMemberInfo(groupId: Int64, groupMemberId: Int64)
case apiContactQueueInfo(contactId: Int64)
case apiGroupMemberQueueInfo(groupId: Int64, groupMemberId: Int64)
case apiSwitchContact(contactId: Int64)
case apiSwitchGroupMember(groupId: Int64, groupMemberId: Int64)
case apiAbortSwitchContact(contactId: Int64)
@@ -228,6 +230,8 @@ public enum ChatCommand {
case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))"
case let .apiContactInfo(contactId): return "/_info @\(contactId)"
case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)"
case let .apiContactQueueInfo(contactId): return "/_queue info @\(contactId)"
case let .apiGroupMemberQueueInfo(groupId, groupMemberId): return "/_queue info #\(groupId) \(groupMemberId)"
case let .apiSwitchContact(contactId): return "/_switch @\(contactId)"
case let .apiSwitchGroupMember(groupId, groupMemberId): return "/_switch #\(groupId) \(groupMemberId)"
case let .apiAbortSwitchContact(contactId): return "/_abort switch @\(contactId)"
@@ -375,6 +379,8 @@ public enum ChatCommand {
case .apiSetMemberSettings: return "apiSetMemberSettings"
case .apiContactInfo: return "apiContactInfo"
case .apiGroupMemberInfo: return "apiGroupMemberInfo"
case .apiContactQueueInfo: return "apiContactQueueInfo"
case .apiGroupMemberQueueInfo: return "apiGroupMemberQueueInfo"
case .apiSwitchContact: return "apiSwitchContact"
case .apiSwitchGroupMember: return "apiSwitchGroupMember"
case .apiAbortSwitchContact: return "apiAbortSwitchContact"
@@ -516,6 +522,7 @@ public enum ChatResponse: Decodable, Error {
case networkConfig(networkConfig: NetCfg)
case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?)
case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?)
case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: QueueInfo)
case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats)
case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats)
case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats)
@@ -678,6 +685,7 @@ public enum ChatResponse: Decodable, Error {
case .networkConfig: return "networkConfig"
case .contactInfo: return "contactInfo"
case .groupMemberInfo: return "groupMemberInfo"
case .queueInfo: return "queueInfo"
case .contactSwitchStarted: return "contactSwitchStarted"
case .groupMemberSwitchStarted: return "groupMemberSwitchStarted"
case .contactSwitchAborted: return "contactSwitchAborted"
@@ -836,6 +844,9 @@ public enum ChatResponse: Decodable, Error {
case let .networkConfig(networkConfig): return String(describing: networkConfig)
case let .contactInfo(u, contact, connectionStats_, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats_: \(String(describing: connectionStats_))\ncustomUserProfile: \(String(describing: customUserProfile))")
case let .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))")
case let .queueInfo(u, rcvMsgInfo, queueInfo):
let msgInfo = if let info = rcvMsgInfo { encodeJSON(rcvMsgInfo) } else { "none" }
return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))")
case let .contactSwitchStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))")
case let .groupMemberSwitchStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))")
case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))")
@@ -2170,3 +2181,42 @@ public enum UserNetworkType: String, Codable {
}
}
}
public struct RcvMsgInfo: Codable {
var msgId: Int64
var msgDeliveryId: Int64
var msgDeliveryStatus: String
var agentMsgId: Int64
var agentMsgMeta: String
}
public struct QueueInfo: Codable {
var qiSnd: Bool
var qiNtf: Bool
var qiSub: QSub?
var qiSize: Int
var qiMsg: MsgInfo?
}
public struct QSub: Codable {
var qSubThread: QSubThread
var qDelivered: String?
}
public enum QSubThread: String, Codable {
case noSub
case subPending
case subThread
case prohibitSub
}
public struct MsgInfo: Codable {
var msgId: String
var msgTs: Date
var msgType: MsgType
}
public enum MsgType: String, Codable {
case message
case quota
}
+1 -1
View File
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: 97a953550f90b14dbf0f9a62077e88df306ebeca
tag: 88f1b727e0e4bfef01a346d818b7f6d94a181b16
source-repository-package
type: git
+1 -1
View File
@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."97a953550f90b14dbf0f9a62077e88df306ebeca" = "15cdvaggk7jghyc0jsxgb17rl327a8z42h0pfga6yrcamcb07mln";
"https://github.com/simplex-chat/simplexmq.git"."88f1b727e0e4bfef01a346d818b7f6d94a181b16" = "0i785dm8sm1c5lydq2kci1a3ix91q3hn89l7x80gnxyqgn4qq2gd";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
+19
View File
@@ -1379,6 +1379,11 @@ processChatCommand' vr = \case
forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
connectionStats <- mapM (withAgent . flip getConnectionServers) (contactConnId ct)
pure $ CRContactInfo user ct connectionStats (fmap fromLocalProfile incognitoProfile)
APIContactQueueInfo contactId -> withUser $ \user -> do
ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId
case activeConn of
Just conn -> getConnQueueInfo user conn
Nothing -> throwChatError $ CEContactNotActive ct
APIGroupInfo gId -> withUser $ \user -> do
(g, s) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> liftIO (getGroupSummary db user gId)
pure $ CRGroupInfo user g s
@@ -1386,6 +1391,11 @@ processChatCommand' vr = \case
(g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
connectionStats <- mapM (withAgent . flip getConnectionServers) (memberConnId m)
pure $ CRGroupMemberInfo user g m connectionStats
APIGroupMemberQueueInfo gId gMemberId -> withUser $ \user -> do
GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user gId gMemberId
case activeConn of
Just conn -> getConnQueueInfo user conn
Nothing -> throwChatError CEGroupMemberNotActive
APISwitchContact contactId -> withUser $ \user -> do
ct <- withStore $ \db -> getContact db vr user contactId
case contactConnId ct of
@@ -1497,6 +1507,8 @@ processChatCommand' vr = \case
groupId <- withStore $ \db -> getGroupIdByName db user gName
processChatCommand $ APIGroupInfo groupId
GroupMemberInfo gName mName -> withMemberName gName mName APIGroupMemberInfo
ContactQueueInfo cName -> withContactName cName APIContactQueueInfo
GroupMemberQueueInfo gName mName -> withMemberName gName mName APIGroupMemberQueueInfo
SwitchContact cName -> withContactName cName APISwitchContact
SwitchGroupMember gName mName -> withMemberName gName mName APISwitchGroupMember
AbortSwitchContact cName -> withContactName cName APIAbortSwitchContact
@@ -2795,6 +2807,9 @@ processChatCommand' vr = \case
pure CIFile {fileId, fileName = takeFileName filePath, fileSize, fileSource = Just cf, fileStatus = CIFSSndStored, fileProtocol = FPLocal}
let ci = mkChatItem cd ciId content ciFile_ Nothing Nothing itemForwarded Nothing False createdAt Nothing createdAt
pure . CRNewChatItem user $ AChatItem SCTLocal SMDSnd (LocalChat nf) ci
getConnQueueInfo user Connection {connId, agentConnId = AgentConnId acId} = do
msgInfo <- withStore' (`getLastRcvMsgInfo` connId)
CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId)
contactCITimed :: Contact -> CM (Maybe CITimed)
contactCITimed ct = sndContactCITimed False ct Nothing
@@ -7361,6 +7376,10 @@ chatCommandP =
("/info #" <|> "/i #") *> (GroupMemberInfo <$> displayName <* A.space <* char_ '@' <*> displayName),
("/info #" <|> "/i #") *> (ShowGroupInfo <$> displayName),
("/info " <|> "/i ") *> char_ '@' *> (ContactInfo <$> displayName),
"/_queue info #" *> (APIGroupMemberQueueInfo <$> A.decimal <* A.space <*> A.decimal),
"/_queue info @" *> (APIContactQueueInfo <$> A.decimal),
("/queue info #" <|> "/qi #") *> (GroupMemberQueueInfo <$> displayName <* A.space <* char_ '@' <*> displayName),
("/queue info " <|> "/qi ") *> char_ '@' *> (ContactQueueInfo <$> displayName),
"/_switch #" *> (APISwitchGroupMember <$> A.decimal <* A.space <*> A.decimal),
"/_switch @" *> (APISwitchContact <$> A.decimal),
"/_abort switch #" *> (APIAbortSwitchGroupMember <$> A.decimal <* A.space <*> A.decimal),
+6
View File
@@ -83,6 +83,7 @@ import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus)
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON)
import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, NtfServer, ProtoServerWithAuth, ProtocolTypeI, QueueId, SMPMsgMeta (..), SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServer, XFTPServerWithAuth, userProtocol)
import Simplex.Messaging.Server.QueueStore.QueueInfo
import Simplex.Messaging.TMap (TMap)
import Simplex.Messaging.Transport (TLS, simplexMQVersion)
import Simplex.Messaging.Transport.Client (TransportHost)
@@ -357,6 +358,8 @@ data ChatCommand
| APIContactInfo ContactId
| APIGroupInfo GroupId
| APIGroupMemberInfo GroupId GroupMemberId
| APIContactQueueInfo ContactId
| APIGroupMemberQueueInfo GroupId GroupMemberId
| APISwitchContact ContactId
| APISwitchGroupMember GroupId GroupMemberId
| APIAbortSwitchContact ContactId
@@ -375,6 +378,8 @@ data ChatCommand
| ContactInfo ContactName
| ShowGroupInfo GroupName
| GroupMemberInfo GroupName ContactName
| ContactQueueInfo ContactName
| GroupMemberQueueInfo GroupName ContactName
| SwitchContact ContactName
| SwitchGroupMember GroupName ContactName
| AbortSwitchContact ContactName
@@ -569,6 +574,7 @@ data ChatResponse
| CRContactInfo {user :: User, contact :: Contact, connectionStats_ :: Maybe ConnectionStats, customUserProfile :: Maybe Profile}
| CRGroupInfo {user :: User, groupInfo :: GroupInfo, groupSummary :: GroupSummary}
| CRGroupMemberInfo {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats_ :: Maybe ConnectionStats}
| CRQueueInfo {user :: User, rcvMsgInfo :: Maybe RcvMsgInfo, queueInfo :: QueueInfo}
| CRContactSwitchStarted {user :: User, contact :: Contact, connectionStats :: ConnectionStats}
| CRGroupMemberSwitchStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats}
| CRContactSwitchAborted {user :: User, contact :: Contact, connectionStats :: ConnectionStats}
+11
View File
@@ -962,6 +962,15 @@ data RcvMsgDelivery = RcvMsgDelivery
}
deriving (Show)
data RcvMsgInfo = RcvMsgInfo
{ msgId :: Int64,
msgDeliveryId :: Int64,
msgDeliveryStatus :: Text,
agentMsgId :: AgentMsgId,
agentMsgMeta :: Text
}
deriving (Show)
data MsgMetaJSON = MsgMetaJSON
{ integrity :: Text,
rcvId :: Int64,
@@ -1332,3 +1341,5 @@ $(JQ.deriveJSON defaultJSON ''MsgMetaJSON)
msgMetaJson :: MsgMeta -> Text
msgMetaJson = decodeLatin1 . LB.toStrict . J.encode . msgMetaToJson
$(JQ.deriveJSON defaultJSON ''RcvMsgInfo)
+18
View File
@@ -23,6 +23,7 @@ module Simplex.Chat.Store.Messages
createNewSndMessage,
createSndMsgDelivery,
createNewMessageAndRcvMsgDelivery,
getLastRcvMsgInfo,
createNewRcvMessage,
updateSndMsgDeliveryStatus,
createPendingGroupMessage,
@@ -226,6 +227,23 @@ createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMs
(msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, snd $ broker agentMsgMeta, currentTs, currentTs, MDSRcvAgent)
pure msg
getLastRcvMsgInfo :: DB.Connection -> Int64 -> IO (Maybe RcvMsgInfo)
getLastRcvMsgInfo db connId =
maybeFirstRow rcvMsgInfo $
DB.query
db
[sql|
SELECT message_id, msg_delivery_id, delivery_status, agent_msg_id, agent_msg_meta
FROM msg_deliveries
WHERE connection_id = ? AND delivery_status IN (?, ?)
ORDER BY created_at DESC, msg_delivery_id DESC
LIMIT 1
|]
(connId, MDSRcvAgent, MDSRcvAcknowledged)
where
rcvMsgInfo (msgId, msgDeliveryId, msgDeliveryStatus, agentMsgId, agentMsgMeta) =
RcvMsgInfo {msgId, msgDeliveryId, msgDeliveryStatus, agentMsgId, agentMsgMeta}
createNewRcvMessage :: forall e. MsgEncodingI e => DB.Connection -> ConnOrGroupId -> NewRcvMessage e -> Maybe SharedMsgId -> Maybe GroupMemberId -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage
createNewRcvMessage db connOrGroupId NewRcvMessage {chatMsgEvent, msgBody} sharedMsgId_ authorMember forwardedByMember =
case connOrGroupId of
+26 -20
View File
@@ -13,7 +13,6 @@ module Simplex.Chat.View where
import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Char (isSpace, toUpper)
@@ -65,7 +64,7 @@ import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON)
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, ProtoServerWithAuth, ProtocolServer (..), ProtocolTypeI, SProtocolType (..))
import qualified Simplex.Messaging.Protocol as SMP
import Simplex.Messaging.Transport.Client (TransportHost (..))
import Simplex.Messaging.Util (bshow, tshow)
import Simplex.Messaging.Util (tshow)
import Simplex.Messaging.Version hiding (version)
import Simplex.RemoteControl.Types (RCCtrlAddress (..))
import System.Console.ANSI.Types
@@ -90,10 +89,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRChatRunning -> ["chat is running"]
CRChatStopped -> ["chat stopped"]
CRChatSuspended -> ["chat suspended"]
CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [plain . bshow $ J.encode chats]
CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [viewJSON chats]
CRChats chats -> viewChats ts tz chats
CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [plain . bshow $ J.encode chat]
CRApiParsedMarkdown ft -> [plain . bshow $ J.encode ft]
CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat]
CRApiParsedMarkdown ft -> [viewJSON ft]
CRUserProtoServers u userServers -> ttyUser u $ viewUserServers userServers testView
CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure
CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl
@@ -101,6 +100,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRContactInfo u ct cStats customUserProfile -> ttyUser u $ viewContactInfo ct cStats customUserProfile
CRGroupInfo u g s -> ttyUser u $ viewGroupInfo g s
CRGroupMemberInfo u g m cStats -> ttyUser u $ viewGroupMemberInfo g m cStats
CRQueueInfo _ msgInfo qInfo ->
[ "last received msg: " <> maybe "none" viewJSON msgInfo,
"server queue info: " <> viewJSON qInfo
]
CRContactSwitchStarted {} -> ["switch started"]
CRGroupMemberSwitchStarted {} -> ["switch started"]
CRContactSwitchAborted {} -> ["switch aborted"]
@@ -220,7 +223,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRSndFileError u (Just ci) _ e -> ttyUser u $ uploadingFile "error" ci <> [plain e]
CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} ->
ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft]
CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [plain . LB.toStrict $ J.encode j]) info_
CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [viewJSON j]) info_
CRContactConnecting u _ -> ttyUser u []
CRContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView
CRContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"]
@@ -322,7 +325,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
]
CRRemoteFileStored rhId (CryptoFile filePath cfArgs_) ->
[plain $ "file " <> filePath <> " stored on remote host " <> show rhId]
<> maybe [] ((: []) . plain . cryptoFileArgsStr testView) cfArgs_
<> maybe [] ((: []) . cryptoFileArgsStr testView) cfArgs_
CRRemoteCtrlList cs -> viewRemoteCtrls cs
CRRemoteCtrlFound {remoteCtrl = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName}, ctrlAppInfo_, appVersion, compatible} ->
[ ("remote controller " <> sShow remoteCtrlId <> " found: ")
@@ -354,8 +357,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
in ("Chat queries" : map viewQuery chatQueries) <> [""] <> ("Agent queries" : map viewQuery agentQueries)
CRDebugLocks {chatLockName, chatEntityLocks, agentLocks} ->
[ maybe "no chat lock" (("chat lock: " <>) . plain) chatLockName,
plain $ "chat entity locks: " <> LB.unpack (J.encode chatEntityLocks),
plain $ "agent locks: " <> LB.unpack (J.encode agentLocks)
"chat entity locks: " <> viewJSON chatEntityLocks,
"agent locks: " <> viewJSON agentLocks
]
CRAgentStats stats -> map (plain . intercalate ",") stats
CRAgentSubs {activeSubs, pendingSubs, removedSubs} ->
@@ -370,12 +373,12 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
("active subscriptions:" : map sShow activeSubscriptions)
<> ("pending subscriptions: " : map sShow pendingSubscriptions)
<> ("removed subscriptions: " : map sShow removedSubscriptions)
CRAgentWorkersSummary {agentWorkersSummary} -> ["agent workers summary: " <> plain (LB.unpack $ J.encode agentWorkersSummary)]
CRAgentWorkersSummary {agentWorkersSummary} -> ["agent workers summary: " <> viewJSON agentWorkersSummary]
CRAgentWorkersDetails {agentWorkersDetails} ->
[ "agent workers details:",
plain . LB.unpack $ J.encode agentWorkersDetails -- this would be huge, but copypastable when has its own line
viewJSON agentWorkersDetails -- this would be huge, but copypastable when has its own line
]
CRAgentMsgCounts {msgCounts} -> ["received messages (total, duplicates):", plain . LB.unpack $ J.encode msgCounts]
CRAgentMsgCounts {msgCounts} -> ["received messages (total, duplicates):", viewJSON msgCounts]
CRAgentQueuesInfo {agentQueuesInfo} ->
[ "agent queues info:",
plain . LB.unpack $ J.encode agentQueuesInfo
@@ -397,7 +400,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRChatError u e -> ttyUser' u $ viewChatError False logLevel testView e
CRChatErrors u errs -> ttyUser' u $ concatMap (viewChatError False logLevel testView) errs
CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)]
CRAppSettings as -> ["app settings: " <> plain (LB.unpack $ J.encode as)]
CRAppSettings as -> ["app settings: " <> viewJSON as]
CRTimedAction _ _ -> []
CRCustomChatResponse u r -> ttyUser' u $ [plain r]
where
@@ -1241,10 +1244,10 @@ viewGroupInfo GroupInfo {groupId, uiThemes, customData} s =
<> viewCustomData customData
viewUITheme :: Maybe UIThemeEntityOverrides -> [StyledString]
viewUITheme = maybe [] (\uiThemes -> ["UI themes: " <> plain (LB.toStrict $ J.encode uiThemes)])
viewUITheme = maybe [] (\uiThemes -> ["UI themes: " <> viewJSON uiThemes])
viewCustomData :: Maybe CustomData -> [StyledString]
viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> plain (LB.toStrict . J.encode $ J.Object v)])
viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> viewJSON (J.Object v)])
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString]
viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProfile = LocalProfile {localAlias, contactLink}, activeConn} stats =
@@ -1686,7 +1689,7 @@ receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> AChatIt
receivingFile_' hu testView status (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir}) =
[plain status <> " receiving " <> fileTransferStr fileId fileName <> fileFrom chat chatDir] <> cfArgsStr cfArgs_ <> getRemoteFileStr
where
cfArgsStr (Just cfArgs) = [plain (cryptoFileArgsStr testView cfArgs) | status == "completed"]
cfArgsStr (Just cfArgs) = [cryptoFileArgsStr testView cfArgs | status == "completed"]
cfArgsStr _ = []
getRemoteFileStr = case hu of
(Just rhId, Just User {userId})
@@ -1707,10 +1710,10 @@ viewLocalFile to CIFile {fileId, fileSource} ts tz = case fileSource of
Just (CryptoFile fPath _) -> sentWithTime_ ts tz [to <> fileTransferStr fileId fPath]
_ -> const []
cryptoFileArgsStr :: Bool -> CryptoFileArgs -> ByteString
cryptoFileArgsStr :: Bool -> CryptoFileArgs -> StyledString
cryptoFileArgsStr testView cfArgs@(CFArgs key nonce)
| testView = LB.toStrict $ J.encode cfArgs
| otherwise = "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce
| testView = viewJSON cfArgs
| otherwise = plain $ "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce
fileFrom :: ChatInfo c -> CIDirection c d -> StyledString
fileFrom (DirectChat ct) CIDirectRcv = " from " <> ttyContact' ct
@@ -1828,7 +1831,7 @@ viewCallAnswer ct WebRTCSession {rtcSession = answer, rtcIceCandidates = iceCand
[ ttyContact' ct <> " continued the WebRTC call",
"To connect, please paste the data below in your browser window you opened earlier and click Connect button",
"",
plain . LB.toStrict . J.encode $ WCCallAnswer {answer, iceCandidates}
viewJSON WCCallAnswer {answer, iceCandidates}
]
callMediaStr :: CallType -> StyledString
@@ -2086,6 +2089,9 @@ viewConnectionEntityInactive entity inactive
| inactive = ["[" <> connEntityLabel entity <> "] connection is marked as inactive"]
| otherwise = ["[" <> connEntityLabel entity <> "] inactive connection is marked as active"]
viewJSON :: J.ToJSON a => a -> StyledString
viewJSON = plain . LB.toStrict . J.encode
connEntityLabel :: ConnectionEntity -> StyledString
connEntityLabel = \case
RcvDirectMsgConnection _ (Just Contact {localDisplayName = c}) -> plain c