diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index bad15ad52..99e8c0284 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -671,18 +671,18 @@ private func connectionErrorAlert(_ r: ChatResponse) -> Alert { } } -func apiDeleteChat(type: ChatType, id: Int64) async throws { - let r = await chatSendCmd(.apiDeleteChat(type: type, id: id), bgTask: false) +func apiDeleteChat(type: ChatType, id: Int64, notify: Bool? = nil) async throws { + let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, notify: notify), bgTask: false) if case .direct = type, case .contactDeleted = r { return } if case .contactConnection = type, case .contactConnectionDeleted = r { return } if case .group = type, case .groupDeletedUser = r { return } throw r } -func deleteChat(_ chat: Chat) async { +func deleteChat(_ chat: Chat, notify: Bool? = nil) async { do { let cInfo = chat.chatInfo - try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId) + try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId, notify: notify) DispatchQueue.main.async { ChatModel.shared.removeChat(cInfo.id) } } catch let error { logger.error("deleteChat apiDeleteChat error: \(responseError(error))") diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 5438eb13b..ec4cc0fc4 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -99,12 +99,12 @@ struct ChatInfoView: View { @Binding var connectionCode: String? @FocusState private var aliasTextFieldFocused: Bool @State private var alert: ChatInfoViewAlert? = nil + @State private var showDeleteContactActionSheet = false @State private var sendReceipts = SendReceipts.userDefault(true) @State private var sendReceiptsUserDefault = true @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false enum ChatInfoViewAlert: Identifiable { - case deleteContactAlert case clearChatAlert case networkStatusAlert case switchAddressAlert @@ -114,7 +114,6 @@ struct ChatInfoView: View { var id: String { switch self { - case .deleteContactAlert: return "deleteContactAlert" case .clearChatAlert: return "clearChatAlert" case .networkStatusAlert: return "networkStatusAlert" case .switchAddressAlert: return "switchAddressAlert" @@ -233,7 +232,6 @@ struct ChatInfoView: View { } .alert(item: $alert) { alertItem in switch(alertItem) { - case .deleteContactAlert: return deleteContactAlert() case .clearChatAlert: return clearChatAlert() case .networkStatusAlert: return networkStatusAlert() case .switchAddressAlert: return switchAddressAlert(switchContactAddress) @@ -242,6 +240,26 @@ struct ChatInfoView: View { case let .error(title, error): return mkAlert(title: title, message: error) } } + .actionSheet(isPresented: $showDeleteContactActionSheet) { + if contact.ready && contact.active { + ActionSheet( + title: Text("Delete contact?\nThis cannot be undone!"), + buttons: [ + .destructive(Text("Delete and notify contact")) { deleteContact(notify: true) }, + .destructive(Text("Delete")) { deleteContact(notify: false) }, + .cancel() + ] + ) + } else { + ActionSheet( + title: Text("Delete contact?\nThis cannot be undone!"), + buttons: [ + .destructive(Text("Delete")) { deleteContact() }, + .cancel() + ] + ) + } + } } private func contactInfoHeader() -> some View { @@ -414,7 +432,7 @@ struct ChatInfoView: View { private func deleteContactButton() -> some View { Button(role: .destructive) { - alert = .deleteContactAlert + showDeleteContactActionSheet = true } label: { Label("Delete contact", systemImage: "trash") .foregroundColor(Color.red) @@ -430,30 +448,23 @@ struct ChatInfoView: View { } } - private func deleteContactAlert() -> Alert { - Alert( - title: Text("Delete contact?"), - message: Text("Contact and all messages will be deleted - this cannot be undone!"), - primaryButton: .destructive(Text("Delete")) { - Task { - do { - try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId) - await MainActor.run { - dismiss() - chatModel.chatId = nil - chatModel.removeChat(chat.chatInfo.id) - } - } catch let error { - logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))") - let a = getErrorAlert(error, "Error deleting contact") - await MainActor.run { - alert = .error(title: a.title, error: a.message) - } - } + private func deleteContact(notify: Bool? = nil) { + Task { + do { + try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notify: notify) + await MainActor.run { + dismiss() + chatModel.chatId = nil + chatModel.removeChat(chat.chatInfo.id) } - }, - secondaryButton: .cancel() - ) + } catch let error { + logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))") + let a = getErrorAlert(error, "Error deleting contact") + await MainActor.run { + alert = .error(title: a.title, error: a.message) + } + } + } } private func clearChatAlert() -> Alert { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index f445ae4b5..be912d666 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -32,6 +32,7 @@ struct ChatListNavLink: View { @State private var showJoinGroupDialog = false @State private var showContactConnectionInfo = false @State private var showInvalidJSON = false + @State private var showDeleteContactActionSheet = false var body: some View { switch chat.chatInfo { @@ -64,17 +65,37 @@ struct ChatListNavLink: View { clearChatButton() } Button { - AlertManager.shared.showAlert( - contact.ready || !contact.active - ? deleteContactAlert(chat.chatInfo) - : deletePendingContactAlert(chat, contact) - ) + if contact.ready || !contact.active { + showDeleteContactActionSheet = true + } else { + AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact)) + } } label: { Label("Delete", systemImage: "trash") } .tint(.red) } .frame(height: rowHeights[dynamicTypeSize]) + .actionSheet(isPresented: $showDeleteContactActionSheet) { + if contact.ready && contact.active { + ActionSheet( + title: Text("Delete contact?\nThis cannot be undone!"), + buttons: [ + .destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } }, + .destructive(Text("Delete")) { Task { await deleteChat(chat, notify: false) } }, + .cancel() + ] + ) + } else { + ActionSheet( + title: Text("Delete contact?\nThis cannot be undone!"), + buttons: [ + .destructive(Text("Delete")) { Task { await deleteChat(chat) } }, + .cancel() + ] + ) + } + } } @ViewBuilder private func groupNavLink(_ groupInfo: GroupInfo) -> some View { @@ -269,17 +290,6 @@ struct ChatListNavLink: View { } } - private func deleteContactAlert(_ chatInfo: ChatInfo) -> Alert { - Alert( - title: Text("Delete contact?"), - message: Text("Contact and all messages will be deleted - this cannot be undone!"), - primaryButton: .destructive(Text("Delete")) { - Task { await deleteChat(chat) } - }, - secondaryButton: .cancel() - ) - } - private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert { Alert( title: Text("Delete group?"), diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 71ff7b88b..51bfb9694 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -356,7 +356,7 @@ struct SimplexLockView: View { var id: Self { self } } - let laDelays: [Int] = [10, 30, 60, 180, 0] + let laDelays: [Int] = [10, 30, 60, 180, 600, 0] func laDelayText(_ t: Int) -> LocalizedStringKey { let m = t / 60 @@ -378,6 +378,7 @@ struct SimplexLockView: View { Text(mode.text) } } + .frame(height: 36) if performLA { Picker("Lock after", selection: $laLockDelay) { let delays = laDelays.contains(laLockDelay) ? laDelays : [laLockDelay] + laDelays @@ -385,6 +386,7 @@ struct SimplexLockView: View { Text(laDelayText(t)) } } + .frame(height: 36) if showChangePassword && laMode == .passcode { Button("Change passcode") { changeLAPassword() diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 53da91b04..5c7220f37 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -89,7 +89,7 @@ public enum ChatCommand { case apiSetConnectionIncognito(connId: Int64, incognito: Bool) case apiConnectPlan(userId: Int64, connReq: String) case apiConnect(userId: Int64, incognito: Bool, connReq: String) - case apiDeleteChat(type: ChatType, id: Int64) + case apiDeleteChat(type: ChatType, id: Int64, notify: Bool?) case apiClearChat(type: ChatType, id: Int64) case apiListContacts(userId: Int64) case apiUpdateProfile(userId: Int64, profile: Profile) @@ -224,7 +224,11 @@ public enum ChatCommand { case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)" case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)" - case let .apiDeleteChat(type, id): return "/_delete \(ref(type, id))" + case let .apiDeleteChat(type, id, notify): if let notify = notify { + return "/_delete \(ref(type, id)) notify=\(onOff(notify))" + } else { + return "/_delete \(ref(type, id))" + } case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" case let .apiListContacts(userId): return "/_contacts \(userId)" case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" 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 8397d2edb..da09ea132 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 @@ -904,8 +904,8 @@ object ChatController { } } - suspend fun apiDeleteChat(type: ChatType, id: Long): Boolean { - val r = sendCmd(CC.ApiDeleteChat(type, id)) + suspend fun apiDeleteChat(type: ChatType, id: Long, notify: Boolean? = null): Boolean { + val r = sendCmd(CC.ApiDeleteChat(type, id, notify)) when { r is CR.ContactDeleted && type == ChatType.Direct -> return true r is CR.ContactConnectionDeleted && type == ChatType.ContactConnection -> return true @@ -1924,7 +1924,7 @@ sealed class CC { class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC() class APIConnectPlan(val userId: Long, val connReq: String): CC() class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC() - class ApiDeleteChat(val type: ChatType, val id: Long): CC() + class ApiDeleteChat(val type: ChatType, val id: Long, val notify: Boolean?): CC() class ApiClearChat(val type: ChatType, val id: Long): CC() class ApiListContacts(val userId: Long): CC() class ApiUpdateProfile(val userId: Long, val profile: Profile): CC() @@ -2034,7 +2034,11 @@ sealed class CC { is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}" is APIConnectPlan -> "/_connect plan $userId $connReq" is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq" - is ApiDeleteChat -> "/_delete ${chatRef(type, id)}" + is ApiDeleteChat -> if (notify != null) { + "/_delete ${chatRef(type, id)} notify=${onOff(notify)}" + } else { + "/_delete ${chatRef(type, id)}" + } is ApiClearChat -> "/_clear chat ${chatRef(type, id)}" is ApiListContacts -> "/_contacts $userId" is ApiUpdateProfile -> "/_profile $userId ${json.encodeToString(profile)}" 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 01173157a..4564a4a6e 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 @@ -196,28 +196,67 @@ sealed class SendReceipts { } fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { - AlertManager.shared.showAlertDialog( + AlertManager.shared.showAlertDialogButtonsColumn( title = generalGetString(MR.strings.delete_contact_question), - text = generalGetString(MR.strings.delete_contact_all_messages_deleted_cannot_undo_warning), - confirmText = generalGetString(MR.strings.delete_verb), - onConfirm = { - withApi { - val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId) - if (r) { - chatModel.removeChat(chatInfo.id) - if (chatModel.chatId.value == chatInfo.id) { - chatModel.chatId.value = null - ModalManager.end.closeModals() + text = AnnotatedString(generalGetString(MR.strings.delete_contact_all_messages_deleted_cannot_undo_warning)), + buttons = { + Column { + if (chatInfo is ChatInfo.Direct && chatInfo.contact.ready && chatInfo.contact.active) { + // Delete and notify contact + SectionItemView({ + AlertManager.shared.hideAlert() + withApi { + deleteContact(chatInfo, chatModel, close, notify = true) + } + }) { + Text(generalGetString(MR.strings.delete_and_notify_contact), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) } - ntfManager.cancelNotificationsForChat(chatInfo.id) - close?.invoke() + // Delete + SectionItemView({ + AlertManager.shared.hideAlert() + withApi { + deleteContact(chatInfo, chatModel, close, notify = false) + } + }) { + Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) + } + } else { + // Delete + SectionItemView({ + AlertManager.shared.hideAlert() + withApi { + deleteContact(chatInfo, chatModel, close) + } + }) { + Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) + } + } + // Cancel + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } } - }, - destructive = true, + } ) } +fun deleteContact(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)?, notify: Boolean? = null) { + withApi { + val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId, notify) + if (r) { + chatModel.removeChat(chatInfo.id) + if (chatModel.chatId.value == chatInfo.id) { + chatModel.chatId.value = null + ModalManager.end.closeModals() + } + ntfManager.cancelNotificationsForChat(chatInfo.id) + close?.invoke() + } + } +} + fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.clear_chat_question), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 211fe5967..84ab87c65 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -317,7 +317,7 @@ private fun showUserGroupsReceiptsAlert( ) } -private val laDelays = listOf(10, 30, 60, 180, 0) +private val laDelays = listOf(10, 30, 60, 180, 600, 0) @Composable fun SimplexLockView( 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 bd59d236d..1ae85cd05 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -350,6 +350,7 @@ Delete contact? Contact and all messages will be deleted - this cannot be undone! + Delete and notify contact Delete contact Set contact name… Connected diff --git a/cabal.project b/cabal.project index 8816c4c88..8ced5d96e 100644 --- a/cabal.project +++ b/cabal.project @@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 2a43717738eaf7a1457f70b48586df19d73a10f3 + tag: 9dfc0e0e924721cf9c4092887b1fe5c954519cb1 source-repository-package type: git @@ -19,7 +19,7 @@ source-repository-package source-repository-package type: git location: https://github.com/kazu-yamamoto/http2.git - tag: b5a1b7200cf5bc7044af34ba325284271f6dff25 + tag: 804fa283f067bd3fd89b8c5f8d25b3047813a517 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 150797a12..3d4786f83 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.4.0.1 +version: 5.4.0.2 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 98ed779d6..d87a1427e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,7 +1,7 @@ { - "https://github.com/simplex-chat/simplexmq.git"."2a43717738eaf7a1457f70b48586df19d73a10f3" = "1iqaiw48zvk0s9s2dgpmsmi78nw1q6nkmfi0n3xiv6q23l40sf6c"; + "https://github.com/simplex-chat/simplexmq.git"."9dfc0e0e924721cf9c4092887b1fe5c954519cb1" = "0c7402727nq0gal64n6jjk3038wqiyw3yczzg0q1b1qkd9nc8r0m"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; - "https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb"; + "https://github.com/kazu-yamamoto/http2.git"."804fa283f067bd3fd89b8c5f8d25b3047813a517" = "1j67wp7rfybfx3ryx08z6gqmzj85j51hmzhgx47ihgmgr47sl895"; "https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd"; "https://github.com/simplex-chat/sqlcipher-simple.git"."5e154a2aeccc33ead6c243ec07195ab673137221" = "1d1gc5wax4vqg0801ajsmx1sbwvd9y7p7b8mmskvqsmpbwgbh0m0"; "https://github.com/simplex-chat/aeson.git"."aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b" = "0jz7kda8gai893vyvj96fy962ncv8dcsx71fbddyy8zrvc88jfrr"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 6e0832824..e2cdf0d75 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.4.0.1 +version: 5.4.0.2 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat @@ -117,6 +117,7 @@ library Simplex.Chat.Migrations.M20231002_conn_initiated Simplex.Chat.Migrations.M20231009_via_group_link_uri_hash Simplex.Chat.Migrations.M20231010_member_settings + Simplex.Chat.Migrations.M20231019_indexes Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 0dafa4bd0..8b28e9c14 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -146,7 +146,10 @@ defaultChatConfig = _defaultSMPServers :: NonEmpty SMPServerWithAuth _defaultSMPServers = L.fromList - [ "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", + [ "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", + "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", + "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion", + "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", @@ -2242,7 +2245,7 @@ processChatCommand = \case case groupLinkId of -- contact address Nothing -> - withStore' (`getUserContactLinkByConnReq` cReqSchemas) >>= \case + withStore' (\db -> getUserContactLinkByConnReq db user cReqSchemas) >>= \case Just _ -> pure $ CPContactAddress CAPOwnLink Nothing -> do withStore' (\db -> getContactConnEntityByConnReqHash db user cReqHashes) >>= \case diff --git a/src/Simplex/Chat/Migrations/M20231019_indexes.hs b/src/Simplex/Chat/Migrations/M20231019_indexes.hs new file mode 100644 index 000000000..40412e177 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20231019_indexes.hs @@ -0,0 +1,32 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20231019_indexes where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20231019_indexes :: Query +m20231019_indexes = + [sql| +DROP INDEX idx_connections_conn_req_inv; +CREATE INDEX idx_connections_conn_req_inv ON connections(user_id, conn_req_inv); + +DROP INDEX idx_groups_via_group_link_uri_hash; +CREATE INDEX idx_groups_via_group_link_uri_hash ON groups(user_id, via_group_link_uri_hash); + +DROP INDEX idx_connections_via_contact_uri_hash; +CREATE INDEX idx_connections_via_contact_uri_hash ON connections(user_id, via_contact_uri_hash); +|] + +down_m20231019_indexes :: Query +down_m20231019_indexes = + [sql| +DROP INDEX idx_connections_conn_req_inv; +CREATE INDEX idx_connections_conn_req_inv ON connections(conn_req_inv); + +DROP INDEX idx_groups_via_group_link_uri_hash; +CREATE INDEX idx_groups_via_group_link_uri_hash ON groups(via_group_link_uri_hash); + +DROP INDEX idx_connections_via_contact_uri_hash; +CREATE INDEX idx_connections_via_contact_uri_hash ON connections(via_contact_uri_hash); +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 7308ef89f..cadb7caf4 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -524,9 +524,6 @@ CREATE INDEX contact_profiles_index ON contact_profiles( full_name ); CREATE INDEX idx_groups_inv_queue_info ON groups(inv_queue_info); -CREATE INDEX idx_connections_via_contact_uri_hash ON connections( - via_contact_uri_hash -); CREATE INDEX idx_contact_requests_xcontact_id ON contact_requests(xcontact_id); CREATE INDEX idx_contacts_xcontact_id ON contacts(xcontact_id); CREATE INDEX idx_messages_shared_msg_id ON messages(shared_msg_id); @@ -738,7 +735,15 @@ CREATE INDEX idx_received_probes_probe_hash ON received_probes(probe_hash); CREATE INDEX idx_sent_probes_created_at ON sent_probes(created_at); CREATE INDEX idx_sent_probe_hashes_created_at ON sent_probe_hashes(created_at); CREATE INDEX idx_received_probes_created_at ON received_probes(created_at); -CREATE INDEX idx_connections_conn_req_inv ON connections(conn_req_inv); +CREATE INDEX idx_connections_conn_req_inv ON connections( + user_id, + conn_req_inv +); CREATE INDEX idx_groups_via_group_link_uri_hash ON groups( + user_id, via_group_link_uri_hash ); +CREATE INDEX idx_connections_via_contact_uri_hash ON connections( + user_id, + via_contact_uri_hash +); diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 4f468b0d6..8677364ea 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -152,9 +152,9 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do userContact_ _ = Left SEUserContactLinkNotFound getConnectionEntityByConnReq :: DB.Connection -> User -> (ConnReqInvitation, ConnReqInvitation) -> IO (Maybe ConnectionEntity) -getConnectionEntityByConnReq db user (cReqSchema1, cReqSchema2) = do +getConnectionEntityByConnReq db user@User {userId} (cReqSchema1, cReqSchema2) = do connId_ <- maybeFirstRow fromOnly $ - DB.query db "SELECT agent_conn_id FROM connections WHERE conn_req_inv IN (?,?) LIMIT 1" (cReqSchema1, cReqSchema2) + DB.query db "SELECT agent_conn_id FROM connections WHERE user_id = ? AND conn_req_inv IN (?,?) LIMIT 1" (userId, cReqSchema1, cReqSchema2) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db user) connId_ -- search connection for connection plan: @@ -162,7 +162,7 @@ getConnectionEntityByConnReq db user (cReqSchema1, cReqSchema2) = do -- this function searches for latest connection with contact so that "known contact" plan would be chosen; -- deleted connections are filtered out to allow re-connecting via same contact address getContactConnEntityByConnReqHash :: DB.Connection -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe ConnectionEntity) -getContactConnEntityByConnReqHash db user (cReqHash1, cReqHash2) = do +getContactConnEntityByConnReqHash db user@User {userId} (cReqHash1, cReqHash2) = do connId_ <- maybeFirstRow fromOnly $ DB.query db @@ -172,12 +172,12 @@ getContactConnEntityByConnReqHash db user (cReqHash1, cReqHash2) = do agent_conn_id, (CASE WHEN contact_id IS NOT NULL THEN 1 ELSE 0 END) AS conn_ord FROM connections - WHERE via_contact_uri_hash IN (?,?) AND conn_status != ? + WHERE user_id = ? AND via_contact_uri_hash IN (?,?) AND conn_status != ? ORDER BY conn_ord DESC, created_at DESC LIMIT 1 ) |] - (cReqHash1, cReqHash2, ConnDeleted) + (userId, cReqHash1, cReqHash2, ConnDeleted) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db user) connId_ getConnectionsToSubscribe :: DB.Connection -> IO ([ConnId], [ConnectionEntity]) diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 6620bc3e4..ea0b2abe9 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -162,7 +162,7 @@ getContactByConnReqHash db user@User {userId} cReqHash = FROM contacts ct JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id JOIN connections c ON c.contact_id = ct.contact_id - WHERE ct.user_id = ? AND c.via_contact_uri_hash = ? AND ct.contact_status = ? AND ct.deleted = 0 + WHERE c.user_id = ? AND c.via_contact_uri_hash = ? AND ct.contact_status = ? AND ct.deleted = 0 ORDER BY c.created_at DESC LIMIT 1 |] diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index c5fd442ce..61aae28f2 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1117,16 +1117,16 @@ getGroupInfo db User {userId, userContactId} groupId = (groupId, userId, userContactId) getGroupInfoByUserContactLinkConnReq :: DB.Connection -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo) -getGroupInfoByUserContactLinkConnReq db user (cReqSchema1, cReqSchema2) = do +getGroupInfoByUserContactLinkConnReq db user@User {userId} (cReqSchema1, cReqSchema2) = do groupId_ <- maybeFirstRow fromOnly $ DB.query db [sql| SELECT group_id FROM user_contact_links - WHERE conn_req_contact IN (?,?) + WHERE user_id = ? AND conn_req_contact IN (?,?) |] - (cReqSchema1, cReqSchema2) + (userId, cReqSchema1, cReqSchema2) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db user) groupId_ getGroupInfoByGroupLinkHash :: DB.Connection -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo) diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 60783f366..357bfd9a2 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -85,6 +85,7 @@ import Simplex.Chat.Migrations.M20230926_contact_status import Simplex.Chat.Migrations.M20231002_conn_initiated import Simplex.Chat.Migrations.M20231009_via_group_link_uri_hash import Simplex.Chat.Migrations.M20231010_member_settings +import Simplex.Chat.Migrations.M20231019_indexes import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -169,7 +170,8 @@ schemaMigrations = ("20230926_contact_status", m20230926_contact_status, Just down_m20230926_contact_status), ("20231002_conn_initiated", m20231002_conn_initiated, Just down_m20231002_conn_initiated), ("20231009_via_group_link_uri_hash", m20231009_via_group_link_uri_hash, Just down_m20231009_via_group_link_uri_hash), - ("20231010_member_settings", m20231010_member_settings, Just down_m20231010_member_settings) + ("20231010_member_settings", m20231010_member_settings, Just down_m20231010_member_settings), + ("20231019_indexes", m20231019_indexes, Just down_m20231019_indexes) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 6eb210bd5..1bf6b251d 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -437,17 +437,17 @@ getUserContactLinkById db userId userContactLinkId = |] (userId, userContactLinkId) -getUserContactLinkByConnReq :: DB.Connection -> (ConnReqContact, ConnReqContact) -> IO (Maybe UserContactLink) -getUserContactLinkByConnReq db (cReqSchema1, cReqSchema2) = +getUserContactLinkByConnReq :: DB.Connection -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe UserContactLink) +getUserContactLinkByConnReq db User {userId} (cReqSchema1, cReqSchema2) = maybeFirstRow toUserContactLink $ DB.query db [sql| SELECT conn_req_contact, auto_accept, auto_accept_incognito, auto_reply_msg_content FROM user_contact_links - WHERE conn_req_contact IN (?,?) + WHERE user_id = ? AND conn_req_contact IN (?,?) |] - (cReqSchema1, cReqSchema2) + (userId, cReqSchema1, cReqSchema2) updateUserAddressAutoAccept :: DB.Connection -> User -> Maybe AutoAccept -> ExceptT StoreError IO UserContactLink updateUserAddressAutoAccept db user@User {userId} autoAccept = do diff --git a/stack.yaml b/stack.yaml index 92489001d..8ea8455d4 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,9 +49,9 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: 2a43717738eaf7a1457f70b48586df19d73a10f3 + commit: 9dfc0e0e924721cf9c4092887b1fe5c954519cb1 - github: kazu-yamamoto/http2 - commit: b5a1b7200cf5bc7044af34ba325284271f6dff25 + commit: 804fa283f067bd3fd89b8c5f8d25b3047813a517 # - ../direct-sqlcipher - github: simplex-chat/direct-sqlcipher commit: 34309410eb2069b029b8fc1872deb1e0db123294 diff --git a/tests/SchemaDump.hs b/tests/SchemaDump.hs index f4538e4b3..f517d13df 100644 --- a/tests/SchemaDump.hs +++ b/tests/SchemaDump.hs @@ -71,7 +71,9 @@ skipComparisonForDownMigrations = -- on down migration idx_chat_items_timed_delete_at index moves down to the end of the file "20230529_indexes", -- table and index definitions move down the file, so fields are re-created as not unique - "20230914_member_probes" + "20230914_member_probes", + -- on down migration idx_connections_via_contact_uri_hash index moves down to the end of the file + "20231019_indexes" ] getSchema :: FilePath -> FilePath -> IO String