Compare commits

..

7 Commits

Author SHA1 Message Date
JRoberts 8a121b4442 ci: disable test step for ubuntu-20.04 (#1270) 2022-10-28 15:27:34 +04:00
JRoberts 1bb7a954f5 ios: update library 2022-10-28 15:21:12 +04:00
JRoberts ddd8bd9061 ci: github build tests timeout (#1267) 2022-10-28 14:46:20 +04:00
JRoberts 179b9e093f core: merge contacts when connecting via group link (#1265) 2022-10-27 23:38:03 +04:00
JRoberts 352a4f3d2a core: clean up incognito profiles (#1262) 2022-10-27 14:25:48 +04:00
JRoberts e06d4e5c85 ios: translations for auto-accept (#1263) 2022-10-27 10:47:48 +04:00
JRoberts 385ebd2298 core: update deleteGroupMember logic and its usages (no items & expiration) (#1258) 2022-10-26 13:37:17 +04:00
13 changed files with 276 additions and 77 deletions
+2 -1
View File
@@ -108,7 +108,8 @@ jobs:
echo "::set-output name=bin_path::$(cabal list-bin simplex-chat)"
- name: Unix test
if: matrix.os != 'windows-latest'
if: matrix.os != 'windows-latest' && matrix.os != 'ubuntu-20.04'
timeout-minutes: 10
shell: bash
run: cabal test --test-show-details=direct
@@ -87,7 +87,7 @@ struct ChatListNavLink: View {
ChatPreviewView(chat: chat)
.frame(height: rowHeights[dynamicTypeSize])
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
joinGroupButton(groupInfo.hostConnCustomUserProfileId)
joinGroupButton()
if groupInfo.canDelete {
deleteGroupChatButton(groupInfo)
}
@@ -137,7 +137,7 @@ struct ChatListNavLink: View {
}
}
private func joinGroupButton(_ hostConnCustomUserProfileId: Int64?) -> some View {
private func joinGroupButton() -> some View {
Button {
joinGroup(chat.chatInfo.apiId)
} label: {
@@ -253,6 +253,11 @@
<target>Inkognito akzeptieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Accept requests" xml:space="preserve">
<source>Accept requests</source>
<target>***Accept requests</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Advanced network settings" xml:space="preserve">
<source>Advanced network settings</source>
<target>Erweiterte Netzwerkeinstellungen</target>
@@ -318,6 +323,11 @@
<target>Bilder automatisch akzeptieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Automatically" xml:space="preserve">
<source>Automatically</source>
<target>***Automatically</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Call already ended!" xml:space="preserve">
<source>Call already ended!</source>
<target>Anruf ist bereits beendet!</target>
@@ -568,6 +578,11 @@
<target>Kontaktname</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact requests" xml:space="preserve">
<source>Contact requests</source>
<target>***Contact requests</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy" xml:space="preserve">
<source>Copy</source>
<target>Kopieren</target>
@@ -2532,6 +2547,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Willkommen %@!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Welcome message" xml:space="preserve">
<source>Welcome message</source>
<target>***Welcome message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="When available" xml:space="preserve">
<source>When available</source>
<target>Wenn verfügbar</target>
@@ -253,6 +253,11 @@
<target>Accept incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Accept requests" xml:space="preserve">
<source>Accept requests</source>
<target>Accept requests</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Advanced network settings" xml:space="preserve">
<source>Advanced network settings</source>
<target>Advanced network settings</target>
@@ -318,6 +323,11 @@
<target>Auto-accept images</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Automatically" xml:space="preserve">
<source>Automatically</source>
<target>Automatically</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Call already ended!" xml:space="preserve">
<source>Call already ended!</source>
<target>Call already ended!</target>
@@ -568,6 +578,11 @@
<target>Contact name</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact requests" xml:space="preserve">
<source>Contact requests</source>
<target>Contact requests</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy" xml:space="preserve">
<source>Copy</source>
<target>Copy</target>
@@ -2532,6 +2547,11 @@ To connect, please ask your contact to create another connection link and check
<target>Welcome %@!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Welcome message" xml:space="preserve">
<source>Welcome message</source>
<target>Welcome message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="When available" xml:space="preserve">
<source>When available</source>
<target>When available</target>
@@ -253,6 +253,11 @@
<target>Принять инкогнито</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Accept requests" xml:space="preserve">
<source>Accept requests</source>
<target>Принимать запросы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Advanced network settings" xml:space="preserve">
<source>Advanced network settings</source>
<target>Настройки сети</target>
@@ -318,6 +323,11 @@
<target>Автоприем изображений</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Automatically" xml:space="preserve">
<source>Automatically</source>
<target>Автоматически</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Call already ended!" xml:space="preserve">
<source>Call already ended!</source>
<target>Звонок уже завершен!</target>
@@ -568,6 +578,11 @@
<target>Имена контактов</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact requests" xml:space="preserve">
<source>Contact requests</source>
<target>Запросы контактов</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy" xml:space="preserve">
<source>Copy</source>
<target>Скопировать</target>
@@ -2532,6 +2547,11 @@ To connect, please ask your contact to create another connection link and check
<target>Здравствуйте %@!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Welcome message" xml:space="preserve">
<source>Welcome message</source>
<target>Приветственное сообщение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="When available" xml:space="preserve">
<source>When available</source>
<target>Когда возможно</target>
+20 -20
View File
@@ -89,11 +89,6 @@
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; };
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; };
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
5CCA7DED2901E32900C8FEBA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DE82901E32800C8FEBA /* libgmp.a */; };
5CCA7DEE2901E32900C8FEBA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DE92901E32800C8FEBA /* libffi.a */; };
5CCA7DEF2901E32900C8FEBA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */; };
5CCA7DF02901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */; };
5CCA7DF12901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */; };
5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */; };
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
@@ -125,6 +120,11 @@
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; };
6432855F290BEE2B00FBE5C8 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6432855A290BEE2B00FBE5C8 /* libffi.a */; };
64328560290BEE2B00FBE5C8 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6432855B290BEE2B00FBE5C8 /* libgmp.a */; };
64328561290BEE2B00FBE5C8 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6432855C290BEE2B00FBE5C8 /* libgmpxx.a */; };
64328562290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6432855D290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu-ghc8.10.7.a */; };
64328563290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6432855E290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu.a */; };
6440CA00288857A10062C672 /* CIGroupEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIGroupEventView.swift */; };
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; };
@@ -290,11 +290,6 @@
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
5CCA7DE82901E32800C8FEBA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5CCA7DE92901E32800C8FEBA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a"; sourceTree = "<group>"; };
5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a"; sourceTree = "<group>"; };
5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptRequestsView.swift; sourceTree = "<group>"; };
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
@@ -324,6 +319,11 @@
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = "<group>"; };
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
640F50E227CF991C001E05C2 /* SMPServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMPServers.swift; sourceTree = "<group>"; };
6432855A290BEE2B00FBE5C8 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
6432855B290BEE2B00FBE5C8 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
6432855C290BEE2B00FBE5C8 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
6432855D290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu-ghc8.10.7.a"; sourceTree = "<group>"; };
6432855E290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu.a"; sourceTree = "<group>"; };
6440C9FF288857A10062C672 /* CIGroupEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupEventView.swift; sourceTree = "<group>"; };
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = "<group>"; };
@@ -375,12 +375,12 @@
buildActionMask = 2147483647;
files = (
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5CCA7DF02901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a in Frameworks */,
5CCA7DEF2901E32900C8FEBA /* libgmpxx.a in Frameworks */,
5CCA7DED2901E32900C8FEBA /* libgmp.a in Frameworks */,
64328561290BEE2B00FBE5C8 /* libgmpxx.a in Frameworks */,
64328562290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu-ghc8.10.7.a in Frameworks */,
6432855F290BEE2B00FBE5C8 /* libffi.a in Frameworks */,
64328560290BEE2B00FBE5C8 /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5CCA7DF12901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a in Frameworks */,
5CCA7DEE2901E32900C8FEBA /* libffi.a in Frameworks */,
64328563290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -435,11 +435,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
5CCA7DE92901E32800C8FEBA /* libffi.a */,
5CCA7DE82901E32800C8FEBA /* libgmp.a */,
5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */,
5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */,
5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */,
6432855A290BEE2B00FBE5C8 /* libffi.a */,
6432855B290BEE2B00FBE5C8 /* libgmp.a */,
6432855C290BEE2B00FBE5C8 /* libgmpxx.a */,
6432855D290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu-ghc8.10.7.a */,
6432855E290BEE2B00FBE5C8 /* libHSsimplex-chat-4.2.0-LeYwSLlznBGLTzzJTly7Iu.a */,
);
path = Libraries;
sourceTree = "<group>";
+12
View File
@@ -161,6 +161,9 @@
/* No comment provided by engineer. */
"Accept incognito" = "Inkognito akzeptieren";
/* No comment provided by engineer. */
"Accept requests" = "***Accept requests";
/* call status */
"accepted call" = "Anruf angenommen";
@@ -209,6 +212,9 @@
/* No comment provided by engineer. */
"Auto-accept images" = "Bilder automatisch akzeptieren";
/* No comment provided by engineer. */
"Automatically" = "***Automatically";
/* integrity error chat item */
"bad message hash" = "Ungültiger Nachrichten-Hash";
@@ -428,6 +434,9 @@
/* No comment provided by engineer. */
"Contact name" = "Kontaktname";
/* No comment provided by engineer. */
"Contact requests" = "***Contact requests";
/* chat item action */
"Copy" = "Kopieren";
@@ -1757,6 +1766,9 @@
/* No comment provided by engineer. */
"Welcome %@!" = "Willkommen %@!";
/* No comment provided by engineer. */
"Welcome message" = "***Welcome message";
/* No comment provided by engineer. */
"When available" = "Wenn verfügbar";
+12
View File
@@ -161,6 +161,9 @@
/* No comment provided by engineer. */
"Accept incognito" = "Принять инкогнито";
/* No comment provided by engineer. */
"Accept requests" = "Принимать запросы";
/* call status */
"accepted call" = " принятый звонок";
@@ -209,6 +212,9 @@
/* No comment provided by engineer. */
"Auto-accept images" = "Автоприем изображений";
/* No comment provided by engineer. */
"Automatically" = "Автоматически";
/* integrity error chat item */
"bad message hash" = "ошибка хэш сообщения";
@@ -428,6 +434,9 @@
/* No comment provided by engineer. */
"Contact name" = "Имена контактов";
/* No comment provided by engineer. */
"Contact requests" = "Запросы контактов";
/* chat item action */
"Copy" = "Скопировать";
@@ -1757,6 +1766,9 @@
/* No comment provided by engineer. */
"Welcome %@!" = "Здравствуйте %@!";
/* No comment provided by engineer. */
"Welcome message" = "Приветственное сообщение";
/* No comment provided by engineer. */
"When available" = "Когда возможно";
+10 -2
View File
@@ -493,7 +493,7 @@ processChatCommand = \case
-- functions below are called in separate transactions to prevent crashes on android
-- (possibly, race condition on integrity check?)
withStore' $ \db -> deleteContactConnectionsAndFiles db userId ct
withStore' $ \db -> deleteContact db userId ct
withStore' $ \db -> deleteContact db user ct
unsetActive $ ActiveC localDisplayName
pure $ CRContactDeleted ct
CTContactConnection -> withChatLock "deleteChat contactConnection" . procCmd $ do
@@ -537,6 +537,8 @@ processChatCommand = \case
maxItemTs_ <- withStore' $ \db -> getGroupMaxItemTs db user gInfo
forM_ filesInfo $ \fileInfo -> deleteFile user fileInfo
withStore' $ \db -> deleteGroupCIs db user gInfo
membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db user gInfo
forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m
gInfo' <- case maxItemTs_ of
Just ts -> do
withStore' $ \db -> updateGroupTs db user gInfo ts
@@ -905,7 +907,10 @@ processChatCommand = \case
ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent $ SGEMemberDeleted memberId (fromLocalProfile memberProfile)) Nothing Nothing
toView . CRNewChatItem $ AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci
deleteMemberConnection user m
withStore' $ \db -> updateGroupMemberStatus db userId m GSMemRemoved
withStore' $ \db ->
checkGroupMemberHasItems db user m >>= \case
Just _ -> updateGroupMemberStatus db userId m GSMemRemoved
Nothing -> deleteGroupMember db user m
pure $ CRUserDeletedMember gInfo m {memberStatus = GSMemRemoved}
APILeaveGroup groupId -> withUser $ \user@User {userId} -> do
Group gInfo@GroupInfo {membership} members <- withStore $ \db -> getGroup db user groupId
@@ -1536,6 +1541,8 @@ expireChatItems user ttl sync = do
maxItemTs_ <- withStore' $ \db -> getGroupMaxItemTs db user gInfo
forM_ filesInfo $ \fileInfo -> deleteFile user fileInfo
withStore' $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff
membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db user gInfo
forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m
withStore' $ \db -> do
ciCount_ <- getGroupCICount db user gInfo
case (maxItemTs_, ciCount_) of
@@ -1703,6 +1710,7 @@ processAgentMessage (Just user@User {userId, profile}) corrId agentConnId agentM
gVar <- asks idsDrg
groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation
withStore $ \db -> createNewContactMemberAsync db gVar user groupId ct GRMember groupConnIds
probeMatchingContacts ct $ contactConnIncognito ct
_ -> pure ()
Just (gInfo@GroupInfo {membership}, m@GroupMember {activeConn}) -> do
when (maybe False ((== ConnReady) . connStatus) activeConn) $ do
@@ -41,7 +41,7 @@ CREATE TABLE display_names (
CREATE TABLE contacts (
contact_id INTEGER PRIMARY KEY,
contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, -- NULL if it's an incognito profile
contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
local_display_name TEXT NOT NULL,
is_user INTEGER NOT NULL DEFAULT 0, -- 1 if this contact is a user
@@ -223,7 +223,7 @@ CREATE TABLE contact_requests (
ON UPDATE CASCADE ON DELETE CASCADE,
agent_invitation_id BLOB NOT NULL,
contact_profile_id INTEGER REFERENCES contact_profiles
ON DELETE SET NULL -- NULL if it's an incognito profile
ON DELETE SET NULL
DEFERRABLE INITIALLY DEFERRED,
local_display_name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
+18 -16
View File
@@ -47,10 +47,10 @@ CREATE TABLE display_names(
) WITHOUT ROWID;
CREATE TABLE contacts(
contact_id INTEGER PRIMARY KEY,
contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, -- NULL if it's an incognito profile
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
local_display_name TEXT NOT NULL,
is_user INTEGER NOT NULL DEFAULT 0, -- 1 if this contact is a user
contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
local_display_name TEXT NOT NULL,
is_user INTEGER NOT NULL DEFAULT 0, -- 1 if this contact is a user
via_group INTEGER REFERENCES groups(group_id) ON DELETE SET NULL,
created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT CHECK(updated_at NOT NULL),
@@ -278,18 +278,20 @@ CREATE TABLE contact_requests(
ON UPDATE CASCADE ON DELETE CASCADE,
agent_invitation_id BLOB NOT NULL,
contact_profile_id INTEGER REFERENCES contact_profiles
ON DELETE SET NULL -- NULL if it's an incognito profile
DEFERRABLE INITIALLY DEFERRED,
local_display_name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT(datetime('now')),
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, updated_at TEXT CHECK(updated_at NOT NULL), xcontact_id BLOB,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON UPDATE CASCADE
ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED,
UNIQUE(user_id, local_display_name),
UNIQUE(user_id, contact_profile_id)
ON DELETE SET NULL
DEFERRABLE INITIALLY DEFERRED,
local_display_name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT(datetime('now')),
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
updated_at TEXT CHECK(updated_at NOT NULL),
xcontact_id BLOB,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON UPDATE CASCADE
ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED,
UNIQUE(user_id, local_display_name),
UNIQUE(user_id, contact_profile_id)
);
CREATE TABLE messages(
message_id INTEGER PRIMARY KEY,
+76 -14
View File
@@ -83,6 +83,7 @@ module Simplex.Chat.Store
getGroupInfoByName,
getGroupMember,
getGroupMembers,
getGroupMembersForExpiration,
deleteGroupConnectionsAndFiles,
deleteGroupItemsAndMembers,
deleteGroup,
@@ -98,6 +99,7 @@ module Simplex.Chat.Store
updateGroupMemberStatus,
updateGroupMemberStatusById,
createNewGroupMember,
checkGroupMemberHasItems,
deleteGroupMember,
deleteGroupMemberConnection,
updateGroupMemberRole,
@@ -558,8 +560,8 @@ deleteContactConnectionsAndFiles db userId Contact {contactId} = do
(userId, contactId)
DB.execute db "DELETE FROM files WHERE user_id = ? AND contact_id = ?" (userId, contactId)
deleteContact :: DB.Connection -> UserId -> Contact -> IO ()
deleteContact db userId Contact {contactId, localDisplayName} = do
deleteContact :: DB.Connection -> User -> Contact -> IO ()
deleteContact db user@User {userId} Contact {contactId, localDisplayName, activeConn = Connection {customUserProfileId}} = do
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND contact_id = ?" (userId, contactId)
ctMember :: (Maybe ContactId) <- maybeFirstRow fromOnly $ DB.query db "SELECT contact_id FROM group_members WHERE user_id = ? AND contact_id = ? LIMIT 1" (userId, contactId)
if isNothing ctMember
@@ -570,6 +572,25 @@ deleteContact db userId Contact {contactId, localDisplayName} = do
currentTs <- getCurrentTime
DB.execute db "UPDATE group_members SET contact_id = NULL, updated_at = ? WHERE user_id = ? AND contact_id = ?" (currentTs, userId, contactId)
DB.execute db "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?" (userId, contactId)
forM_ customUserProfileId $ \profileId -> deleteUnusedIncognitoProfileById_ db user profileId
deleteUnusedIncognitoProfileById_ :: DB.Connection -> User -> ProfileId -> IO ()
deleteUnusedIncognitoProfileById_ db User {userId} profile_id =
DB.executeNamed
db
[sql|
DELETE FROM contact_profiles
WHERE user_id = :user_id AND contact_profile_id = :profile_id AND incognito = 1
AND 1 NOT IN (
SELECT 1 FROM connections
WHERE user_id = :user_id AND custom_user_profile_id = :profile_id LIMIT 1
)
AND 1 NOT IN (
SELECT 1 FROM group_members
WHERE user_id = :user_id AND member_profile_id = :profile_id LIMIT 1
)
|]
[":user_id" := userId, ":profile_id" := profile_id]
deleteContactProfile_ :: DB.Connection -> UserId -> ContactId -> IO ()
deleteContactProfile_ db userId contactId =
@@ -1324,6 +1345,10 @@ mergeContactRecords db userId Contact {contactId = toContactId} Contact {contact
db
"UPDATE group_members SET invited_by = ?, updated_at = ? WHERE invited_by = ? AND user_id = ?"
(toContactId, currentTs, fromContactId, userId)
DB.execute
db
"UPDATE chat_items SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?"
(toContactId, currentTs, fromContactId, userId)
DB.executeNamed
db
[sql|
@@ -1676,9 +1701,7 @@ deleteGroupItemsAndMembers :: DB.Connection -> User -> GroupInfo -> [GroupMember
deleteGroupItemsAndMembers db user@User {userId} GroupInfo {groupId} members = do
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND group_id = ?" (userId, groupId)
DB.execute db "DELETE FROM group_members WHERE user_id = ? AND group_id = ?" (userId, groupId)
forM_ members $ \m@GroupMember {groupMemberId, memberContactId, memberContactProfileId} -> unless (isJust memberContactId) $ do
sameProfileMember :: (Maybe GroupMemberId) <- maybeFirstRow fromOnly $ DB.query db "SELECT group_member_id FROM group_members WHERE user_id = ? AND contact_profile_id = ? AND group_member_id != ? LIMIT 1" (userId, memberContactProfileId, groupMemberId)
unless (isJust sameProfileMember) $ deleteMemberProfileAndName_ db user m
forM_ members $ \m -> cleanupMemberContactAndProfile_ db user m
deleteGroup :: DB.Connection -> User -> GroupInfo -> IO ()
deleteGroup db User {userId} GroupInfo {groupId, localDisplayName} = do
@@ -1779,6 +1802,32 @@ getGroupMembers db user@User {userId, userContactId} GroupInfo {groupId} = do
|]
(groupId, userId, userContactId)
getGroupMembersForExpiration :: DB.Connection -> User -> GroupInfo -> IO [GroupMember]
getGroupMembersForExpiration db user@User {userId, userContactId} GroupInfo {groupId} = do
map (toContactMember user)
<$> DB.query
db
[sql|
SELECT
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN connections c ON c.connection_id = (
SELECT max(cc.connection_id)
FROM connections cc
where cc.group_member_id = m.group_member_id
)
WHERE m.group_id = ? AND m.user_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)
AND m.member_status IN (?, ?, ?)
AND m.group_member_id NOT IN (
SELECT DISTINCT group_member_id FROM chat_items
)
|]
(groupId, userId, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted)
toContactMember :: User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember
toContactMember User {userContactId} (memberRow :. connRow) =
(toGroupMember userContactId memberRow) {activeConn = toMaybeConnection connRow}
@@ -1986,19 +2035,32 @@ createNewMember_
groupMemberId <- insertedRowId db
pure GroupMember {groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus, invitedBy, localDisplayName, memberProfile = toLocalProfile memberContactProfileId memberProfile "", memberContactId, memberContactProfileId, activeConn}
checkGroupMemberHasItems :: DB.Connection -> User -> GroupMember -> IO (Maybe ChatItemId)
checkGroupMemberHasItems db User {userId} GroupMember {groupMemberId, groupId} =
maybeFirstRow fromOnly $ DB.query db "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_member_id = ? LIMIT 1" (userId, groupId, groupMemberId)
deleteGroupMember :: DB.Connection -> User -> GroupMember -> IO ()
deleteGroupMember db user@User {userId} m@GroupMember {groupMemberId, groupId, memberContactId, memberContactProfileId} = do
deleteGroupMember db user@User {userId} m@GroupMember {groupMemberId, groupId} = do
deleteGroupMemberConnection db user m
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND group_id = ? AND group_member_id = ?" (userId, groupId, groupMemberId)
DB.execute db "DELETE FROM group_members WHERE user_id = ? AND group_member_id = ?" (userId, groupMemberId)
unless (isJust memberContactId) $ do
sameProfileMember :: (Maybe GroupMemberId) <- maybeFirstRow fromOnly $ DB.query db "SELECT group_member_id FROM group_members WHERE user_id = ? AND contact_profile_id = ? AND group_member_id != ? LIMIT 1" (userId, memberContactProfileId, groupMemberId)
unless (isJust sameProfileMember) $ deleteMemberProfileAndName_ db user m
cleanupMemberContactAndProfile_ db user m
deleteMemberProfileAndName_ :: DB.Connection -> User -> GroupMember -> IO ()
deleteMemberProfileAndName_ db User {userId} GroupMember {memberContactProfileId, localDisplayName} = do
DB.execute db "DELETE FROM contact_profiles WHERE user_id = ? AND contact_profile_id = ?" (userId, memberContactProfileId)
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
-- it's important this function is used in transaction after the actual group_members record is deleted, see checkIncognitoProfileInUse_
cleanupMemberContactAndProfile_ :: DB.Connection -> User -> GroupMember -> IO ()
cleanupMemberContactAndProfile_ db user@User {userId} m@GroupMember {groupMemberId, localDisplayName, memberContactId, memberContactProfileId, memberProfile = LocalProfile {profileId}} =
case memberContactId of
Just contactId ->
runExceptT (getContact db userId contactId) >>= \case
Right ct@Contact {activeConn = Connection {connLevel, viaGroupLink}, contactUsed} ->
unless ((connLevel == 0 && not viaGroupLink) || contactUsed) $ deleteContact db user ct
_ -> pure ()
Nothing -> do
sameProfileMember :: (Maybe GroupMemberId) <- maybeFirstRow fromOnly $ DB.query db "SELECT group_member_id FROM group_members WHERE user_id = ? AND contact_profile_id = ? AND group_member_id != ? LIMIT 1" (userId, memberContactProfileId, groupMemberId)
unless (isJust sameProfileMember) $ do
DB.execute db "DELETE FROM contact_profiles WHERE user_id = ? AND contact_profile_id = ?" (userId, memberContactProfileId)
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
when (memberIncognito m) $ deleteUnusedIncognitoProfileById_ db user profileId
deleteGroupMemberConnection :: DB.Connection -> User -> GroupMember -> IO ()
deleteGroupMemberConnection db User {userId} GroupMember {groupMemberId} =
@@ -3122,7 +3184,7 @@ getDirectChatPreviews_ db User {userId} = do
) ChatStats ON ChatStats.contact_id = ct.contact_id
LEFT JOIN chat_items ri ON i.quoted_shared_msg_id = ri.shared_msg_id
WHERE ct.user_id = ?
AND ((c.conn_level = 0 AND c.via_group_link = 0) OR i.chat_item_id IS NOT NULL OR ct.contact_used = 1)
AND ((c.conn_level = 0 AND c.via_group_link = 0) OR ct.contact_used = 1)
AND c.connection_id = (
SELECT cc_connection_id FROM (
SELECT
+62 -20
View File
@@ -138,6 +138,7 @@ chatTests = do
it "set chat item TTL" testSetChatItemTTL
describe "group links" $ do
it "create group link, join via group link" testGroupLink
it "sending message to contact created via group link marks it used" testGroupLinkContactUsed
it "create group link, join via group link - incognito membership" testGroupLinkIncognitoMembership
it "deleting invited member does not leave broken chat item" testGroupLinkDeleteInvitedMemberNoBrokenItem
@@ -768,7 +769,13 @@ testGroupDelete =
cath <## "#team: you deleted the group"
alice <##> bob
alice <##> cath
bob <##> cath
-- unused group contacts are deleted
bob ##> "@cath hi"
bob <## "no contact cath"
(cath </)
cath ##> "@bob hi"
cath <## "no contact bob"
(bob </)
testGroupSameName :: IO ()
testGroupSameName =
@@ -3349,24 +3356,23 @@ testGroupLink =
cath ##> ("/c " <> gLink)
cath <## "connection request sent!"
alice <## "cath_1 (Catherine): accepting request to join group #team..."
-- if contact existed it is merged
concurrentlyN_
[ do
alice <## "cath_1 (Catherine): contact is connected"
alice <## "cath_1 invited to group #team via your group link",
alice <## "cath_1 invited to group #team via your group link"
alice <## "contact cath_1 is merged into cath"
alice <## "use @cath <message> to send messages",
do
cath <## "alice_1 (Alice): contact is connected"
cath <## "#team: alice_1 invites you to join the group as member"
cath <## "contact alice_1 is merged into alice"
cath <## "use @alice <message> to send messages"
cath <## "#team: alice invites you to join the group as member"
cath <## "use /j team to accept"
]
-- sending message to contact marks it as used
alice @@@ [("@cath", "hey"), ("@bob", "hey"), ("#team", "invited via your group link")]
alice #> "@cath_1 hello"
cath <# "alice_1> hello"
alice #$> ("/clear cath_1", id, "cath_1: all messages are removed locally ONLY")
alice @@@ [("@cath_1", ""), ("@cath", "hey"), ("@bob", "hey"), ("#team", "invited via your group link")]
cath ##> "/j team"
concurrentlyN_
[ alice <## "#team: cath_1 joined the group",
[ alice <## "#team: cath joined the group",
do
cath <## "#team: you joined the group"
cath <## "#team: member bob (Bob) is connected",
@@ -3377,14 +3383,14 @@ testGroupLink =
alice #> "#team hello"
concurrently_
(bob <# "#team alice> hello")
(cath <# "#team alice_1> hello")
(cath <# "#team alice> hello")
bob #> "#team hi there"
concurrently_
(alice <# "#team bob> hi there")
(cath <# "#team bob> hi there")
cath #> "#team hey team"
concurrently_
(alice <# "#team cath_1> hey team")
(alice <# "#team cath> hey team")
(bob <# "#team cath> hey team")
-- leaving team removes link
@@ -3394,11 +3400,45 @@ testGroupLink =
alice <## "#team: you left the group"
alice <## "use /d #team to delete the group",
bob <## "#team: alice left the group",
cath <## "#team: alice_1 left the group"
cath <## "#team: alice left the group"
]
alice ##> "/show link #team"
alice <## "no group link, to create: /create link #team"
testGroupLinkContactUsed :: IO ()
testGroupLinkContactUsed =
testChat2 aliceProfile bobProfile $
\alice bob -> do
alice ##> "/g team"
alice <## "group #team is created"
alice <## "use /a team <name> to add members"
alice ##> "/show link #team"
alice <## "no group link, to create: /create link #team"
alice ##> "/create link #team"
gLink <- getGroupLink alice "team" True
alice ##> "/show link #team"
_ <- getGroupLink alice "team" False
alice ##> "/create link #team"
alice <## "you already have link for this group, to show: /show link #team"
bob ##> ("/c " <> gLink)
bob <## "connection request sent!"
alice <## "bob (Bob): accepting request to join group #team..."
concurrentlyN_
[ do
alice <## "bob (Bob): contact is connected"
alice <## "bob invited to group #team via your group link",
do
bob <## "alice (Alice): contact is connected"
bob <## "#team: alice invites you to join the group as member"
bob <## "use /j team to accept"
]
-- sending message to contact marks it as used
alice @@@ [("#team", "invited via your group link")]
alice #> "@bob hello"
bob <# "alice> hello"
alice #$> ("/clear bob", id, "bob: all messages are removed locally ONLY")
alice @@@ [("@bob", ""), ("#team", "invited via your group link")]
testGroupLinkIncognitoMembership :: IO ()
testGroupLinkIncognitoMembership =
testChat4 aliceProfile bobProfile cathProfile danProfile $
@@ -3557,8 +3597,10 @@ testGroupLinkDeleteInvitedMemberNoBrokenItem =
alice <## "#team: you removed bob from the group"
alice #$> ("/_get chat #1 count=100", chat, [])
alice @@@ [("#team", "")]
alice <##> bob
alice @@@ [("@bob", "hey"), ("#team", "")]
-- removing member deletes unused group contact
alice ##> "@bob hi"
alice <## "no contact bob"
(bob </)
bob ##> "/j team"
bob <## "error: connection authorization failed - this could happen if connection was deleted, secured with different credentials, or due to a bug - please re-create the connection"
-- repeat request is prohibited because of the re-used XContactId, until contact is deleted
@@ -3568,11 +3610,11 @@ testGroupLinkDeleteInvitedMemberNoBrokenItem =
bob <## "alice: contact is deleted"
bob ##> ("/c " <> gLink)
bob <## "connection request sent!"
alice <## "bob_1 (Bob): accepting request to join group #team..."
alice <## "bob (Bob): accepting request to join group #team..."
concurrentlyN_
[ do
alice <## "bob_1 (Bob): contact is connected"
alice <## "bob_1 invited to group #team via your group link",
alice <## "bob (Bob): contact is connected"
alice <## "bob invited to group #team via your group link",
do
bob <## "alice_1 (Alice): contact is connected"
bob <## "#team_1 (team): alice_1 invites you to join the group as member"
@@ -3580,12 +3622,12 @@ testGroupLinkDeleteInvitedMemberNoBrokenItem =
]
bob ##> "/j team_1"
concurrently_
(alice <## "#team: bob_1 joined the group")
(alice <## "#team: bob joined the group")
(bob <## "#team_1: you joined the group")
alice #> "#team hello"
bob <# "#team_1 alice_1> hello"
bob #> "#team_1 hi there"
alice <# "#team bob_1> hi there"
alice <# "#team bob> hi there"
withTestChatContactConnected :: String -> (TestCC -> IO a) -> IO a
withTestChatContactConnected dbPrefix action =