Compare commits
19 Commits
v5.5.5-arm
...
v5.5.2-arm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9514acf6aa | ||
|
|
40d54f3f16 | ||
|
|
3249c5e463 | ||
|
|
e440fba582 | ||
|
|
43cedbce26 | ||
|
|
ebad009553 | ||
|
|
35cf113d98 | ||
|
|
e82d2cbea2 | ||
|
|
a0e056994e | ||
|
|
e4d8ea17fc | ||
|
|
e69db17804 | ||
|
|
9282cca396 | ||
|
|
ad2eaeb004 | ||
|
|
3f44c9af24 | ||
|
|
4296515473 | ||
|
|
88830a2e09 | ||
|
|
c8a269e391 | ||
|
|
a45897007c | ||
|
|
42ea1df342 |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Haskell
|
||||
uses: haskell/actions/setup@v2
|
||||
uses: haskell-actions/setup@v2
|
||||
with:
|
||||
ghc-version: ${{ matrix.ghc }}
|
||||
cabal-version: "3.10.1.0"
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
|
||||
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
|
||||
run: |
|
||||
scripts/build-desktop-mac.sh
|
||||
scripts/ci/build-desktop-mac.sh
|
||||
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
|
||||
@@ -277,9 +277,7 @@ jobs:
|
||||
# Unix /
|
||||
|
||||
# / Windows
|
||||
|
||||
# * In powershell multiline commands do not fail if individual commands fail - https://github.community/t/multiline-commands-on-windows-do-not-fail-if-individual-commands-fail/16753
|
||||
# * And GitHub Actions does not support parameterizing shell in a matrix job - https://github.community/t/using-matrix-to-specify-shell-is-it-possible/17065
|
||||
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
@@ -252,6 +252,12 @@ func apiSetFilesFolder(filesFolder: String) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
let r = chatSendCmdSync(.apiSetXFTPConfig(config: cfg))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
|
||||
let r = chatSendCmdSync(.apiSetEncryptLocalFiles(enable: enable))
|
||||
if case .cmdOk = r { return }
|
||||
@@ -1243,6 +1249,7 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni
|
||||
}
|
||||
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
|
||||
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
|
||||
try setXFTPConfig(getXFTPCfg())
|
||||
try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
|
||||
m.chatInitialized = true
|
||||
m.currentUser = try apiGetActiveUser()
|
||||
@@ -1854,9 +1861,7 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async {
|
||||
let cItem = aChatItem.chatItem
|
||||
if active(user) {
|
||||
if await MainActor.run(body: { m.upsertChatItem(cInfo, cItem) }) {
|
||||
if cItem.showNotification {
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
}
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,25 @@ struct DeveloperView: View {
|
||||
} footer: {
|
||||
(developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option.")
|
||||
}
|
||||
|
||||
// Section {
|
||||
// settingsRow("arrow.up.doc") {
|
||||
// Toggle("Send videos and files via XFTP", isOn: $xftpSendEnabled)
|
||||
// .onChange(of: xftpSendEnabled) { _ in
|
||||
// do {
|
||||
// try setXFTPConfig(getXFTPCfg())
|
||||
// } catch {
|
||||
// logger.error("setXFTPConfig: cannot set XFTP config \(responseError(error))")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } header: {
|
||||
// Text("Experimental")
|
||||
// } footer: {
|
||||
// if xftpSendEnabled {
|
||||
// Text("v4.6.1+ is required to receive via XFTP.")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,6 +453,7 @@ var receiverStarted = false
|
||||
let startLock = DispatchSemaphore(value: 1)
|
||||
let suspendLock = DispatchSemaphore(value: 1)
|
||||
var networkConfig: NetCfg = getNetCfg()
|
||||
let xftpConfig: XFTPFileConfig? = getXFTPCfg()
|
||||
|
||||
// startChat uses semaphore startLock to ensure that only one didReceive thread can start chat controller
|
||||
// Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active
|
||||
@@ -498,6 +499,7 @@ func doStartChat() -> DBMigrationResult? {
|
||||
try setNetworkConfig(networkConfig)
|
||||
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
|
||||
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
|
||||
try setXFTPConfig(xftpConfig)
|
||||
try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
|
||||
// prevent suspension while starting chat
|
||||
suspendLock.wait()
|
||||
@@ -731,6 +733,12 @@ func apiSetFilesFolder(filesFolder: String) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
let r = sendSimpleXCmd(.apiSetXFTPConfig(config: cfg))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
|
||||
let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable))
|
||||
if case .cmdOk = r { return }
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
|
||||
5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C13730A28156D2700F43030 /* ContactConnectionView.swift */; };
|
||||
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; };
|
||||
5C29C3A52B6D09B2003DF84C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C29C3A02B6D09B2003DF84C /* libgmpxx.a */; };
|
||||
5C29C3A62B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C29C3A12B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz.a */; };
|
||||
5C29C3A72B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C29C3A22B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz-ghc9.6.3.a */; };
|
||||
5C29C3A82B6D09B2003DF84C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C29C3A32B6D09B2003DF84C /* libgmp.a */; };
|
||||
5C29C3A92B6D09B2003DF84C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C29C3A42B6D09B2003DF84C /* libffi.a */; };
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
|
||||
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
|
||||
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; };
|
||||
@@ -90,11 +95,6 @@
|
||||
5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */; };
|
||||
5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA91282713FD00B3292C /* CreateProfile.swift */; };
|
||||
5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA992827FD8800B3292C /* HowItWorks.swift */; };
|
||||
5CB1CE922B86660100963938 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE8D2B86660100963938 /* libgmp.a */; };
|
||||
5CB1CE932B86660100963938 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE8E2B86660100963938 /* libgmpxx.a */; };
|
||||
5CB1CE942B86660100963938 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE8F2B86660100963938 /* libffi.a */; };
|
||||
5CB1CE952B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE902B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a */; };
|
||||
5CB1CE962B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE912B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a */; };
|
||||
5CB2084F28DA4B4800D024EC /* RTCServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2084E28DA4B4800D024EC /* RTCServers.swift */; };
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */; };
|
||||
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E62868D76D001FD2EF /* NotificationsView.swift */; };
|
||||
@@ -164,6 +164,11 @@
|
||||
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
|
||||
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
|
||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
|
||||
6449333A2AF8E51000AC506E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933352AF8E51000AC506E /* libgmpxx.a */; };
|
||||
6449333B2AF8E51000AC506E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933362AF8E51000AC506E /* libgmp.a */; };
|
||||
6449333C2AF8E51000AC506E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933372AF8E51000AC506E /* libffi.a */; };
|
||||
6449333D2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */; };
|
||||
6449333E2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */; };
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||
@@ -278,6 +283,11 @@
|
||||
5C245F3C2B501E98001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5C245F3D2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = "tr.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5C245F3E2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5C29C3A02B6D09B2003DF84C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C29C3A12B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz.a"; sourceTree = "<group>"; };
|
||||
5C29C3A22B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C29C3A32B6D09B2003DF84C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C29C3A42B6D09B2003DF84C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = "<group>"; };
|
||||
5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; };
|
||||
5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
||||
@@ -372,11 +382,6 @@
|
||||
5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXInfo.swift; sourceTree = "<group>"; };
|
||||
5CB0BA91282713FD00B3292C /* CreateProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfile.swift; sourceTree = "<group>"; };
|
||||
5CB0BA992827FD8800B3292C /* HowItWorks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowItWorks.swift; sourceTree = "<group>"; };
|
||||
5CB1CE8D2B86660100963938 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CB1CE8E2B86660100963938 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CB1CE8F2B86660100963938 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CB1CE902B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5CB1CE912B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a"; sourceTree = "<group>"; };
|
||||
5CB2084E28DA4B4800D024EC /* RTCServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTCServers.swift; sourceTree = "<group>"; };
|
||||
5CB2085428DE647400D024EC /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = "<group>"; };
|
||||
@@ -450,6 +455,11 @@
|
||||
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
|
||||
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
||||
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
|
||||
644933352AF8E51000AC506E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
644933362AF8E51000AC506E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
644933372AF8E51000AC506E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a"; sourceTree = "<group>"; };
|
||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
||||
@@ -514,13 +524,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CB1CE932B86660100963938 /* libgmpxx.a in Frameworks */,
|
||||
5C29C3A62B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz.a in Frameworks */,
|
||||
5C29C3A52B6D09B2003DF84C /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CB1CE962B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a in Frameworks */,
|
||||
5CB1CE922B86660100963938 /* libgmp.a in Frameworks */,
|
||||
5CB1CE952B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a in Frameworks */,
|
||||
5CB1CE942B86660100963938 /* libffi.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5C29C3A92B6D09B2003DF84C /* libffi.a in Frameworks */,
|
||||
5C29C3A82B6D09B2003DF84C /* libgmp.a in Frameworks */,
|
||||
5C29C3A72B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz-ghc9.6.3.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -582,11 +592,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CB1CE8F2B86660100963938 /* libffi.a */,
|
||||
5CB1CE8D2B86660100963938 /* libgmp.a */,
|
||||
5CB1CE8E2B86660100963938 /* libgmpxx.a */,
|
||||
5CB1CE902B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a */,
|
||||
5CB1CE912B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a */,
|
||||
5C29C3A42B6D09B2003DF84C /* libffi.a */,
|
||||
5C29C3A32B6D09B2003DF84C /* libgmp.a */,
|
||||
5C29C3A02B6D09B2003DF84C /* libgmpxx.a */,
|
||||
5C29C3A22B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz-ghc9.6.3.a */,
|
||||
5C29C3A12B6D09B2003DF84C /* libHSsimplex-chat-5.5.2.0-B3iqnZovI7Z5cYCa3ekeAz.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1509,7 +1519,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 196;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1531,7 +1541,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
MARKETING_VERSION = 5.5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1552,7 +1562,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 196;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1574,7 +1584,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
MARKETING_VERSION = 5.5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1633,7 +1643,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 196;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1646,7 +1656,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
MARKETING_VERSION = 5.5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1665,7 +1675,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 196;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1678,7 +1688,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
MARKETING_VERSION = 5.5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1697,7 +1707,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 196;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1721,7 +1731,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
MARKETING_VERSION = 5.5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1743,7 +1753,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 196;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1767,7 +1777,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
MARKETING_VERSION = 5.5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -31,6 +31,7 @@ public enum ChatCommand {
|
||||
case apiSuspendChat(timeoutMicroseconds: Int)
|
||||
case setTempFolder(tempFolder: String)
|
||||
case setFilesFolder(filesFolder: String)
|
||||
case apiSetXFTPConfig(config: XFTPFileConfig?)
|
||||
case apiSetEncryptLocalFiles(enable: Bool)
|
||||
case apiExportArchive(config: ArchiveConfig)
|
||||
case apiImportArchive(config: ArchiveConfig)
|
||||
@@ -161,6 +162,11 @@ public enum ChatCommand {
|
||||
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
|
||||
case let .setTempFolder(tempFolder): return "/_temp_folder \(tempFolder)"
|
||||
case let .setFilesFolder(filesFolder): return "/_files_folder \(filesFolder)"
|
||||
case let .apiSetXFTPConfig(cfg): if let cfg = cfg {
|
||||
return "/_xftp on \(encodeJSON(cfg))"
|
||||
} else {
|
||||
return "/_xftp off"
|
||||
}
|
||||
case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))"
|
||||
case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))"
|
||||
case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))"
|
||||
@@ -305,6 +311,7 @@ public enum ChatCommand {
|
||||
case .apiSuspendChat: return "apiSuspendChat"
|
||||
case .setTempFolder: return "setTempFolder"
|
||||
case .setFilesFolder: return "setFilesFolder"
|
||||
case .apiSetXFTPConfig: return "apiSetXFTPConfig"
|
||||
case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles"
|
||||
case .apiExportArchive: return "apiExportArchive"
|
||||
case .apiImportArchive: return "apiImportArchive"
|
||||
@@ -998,6 +1005,10 @@ struct ComposedMessage: Encodable {
|
||||
var msgContent: MsgContent
|
||||
}
|
||||
|
||||
public struct XFTPFileConfig: Encodable {
|
||||
var minFileSize: Int64
|
||||
}
|
||||
|
||||
public struct ArchiveConfig: Encodable {
|
||||
var archivePath: String
|
||||
var disableCompression: Bool?
|
||||
|
||||
@@ -265,6 +265,10 @@ public class Default<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public func getXFTPCfg() -> XFTPFileConfig {
|
||||
return XFTPFileConfig(minFileSize: 0)
|
||||
}
|
||||
|
||||
public func getNetCfg() -> NetCfg {
|
||||
let onionHosts = networkUseOnionHostsGroupDefault.get()
|
||||
let (hostMode, requiredHostMode) = onionHosts.hostMode
|
||||
|
||||
@@ -71,7 +71,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
Log.d(TAG, "onStateChanged: $event")
|
||||
withLongRunningApi {
|
||||
withBGApi {
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_START -> {
|
||||
isAppOnForeground = true
|
||||
|
||||
@@ -14,27 +14,17 @@ import chat.simplex.common.views.helpers.*
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import chat.simplex.res.MR
|
||||
import kotlin.math.min
|
||||
|
||||
actual fun ClipboardManager.shareText(text: String) {
|
||||
var text = text
|
||||
for (i in 10 downTo 1) {
|
||||
try {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, text)
|
||||
type = "text/plain"
|
||||
flags = FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
shareIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||
androidAppContext.startActivity(shareIntent)
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to share text: ${e.stackTraceToString()}")
|
||||
text = text.substring(0, min(i * 1000, text.length))
|
||||
}
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, text)
|
||||
type = "text/plain"
|
||||
flags = FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
shareIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||
androidAppContext.startActivity(shareIntent)
|
||||
}
|
||||
|
||||
actual fun shareFile(text: String, fileSource: CryptoFile) {
|
||||
|
||||
@@ -114,8 +114,7 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.app_was_crashed),
|
||||
text = e.stackTraceToString(),
|
||||
shareText = true
|
||||
text = e.stackTraceToString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,21 +451,7 @@ object ChatController {
|
||||
}
|
||||
try {
|
||||
val msg = recvMsg(ctrl)
|
||||
if (msg != null) {
|
||||
val finishedWithoutTimeout = withTimeoutOrNull(60_000L) {
|
||||
processReceivedMsg(msg)
|
||||
}
|
||||
if (finishedWithoutTimeout == null) {
|
||||
Log.e(TAG, "Timeout reached while processing received message: " + msg.resp.responseType)
|
||||
if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.possible_slow_function_title),
|
||||
text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.resp.responseType + "\n" + Exception().stackTraceToString()),
|
||||
shareText = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg != null) processReceivedMsg(msg)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "ChatController recvMsg/processReceivedMsg exception: " + e.stackTraceToString());
|
||||
} catch (e: Throwable) {
|
||||
@@ -631,6 +617,12 @@ object ChatController {
|
||||
throw Error("failed to set remote hosts folder: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun apiSetXFTPConfig(cfg: XFTPFileConfig?) {
|
||||
val r = sendCmd(null, CC.ApiSetXFTPConfig(cfg))
|
||||
if (r is CR.CmdOk) return
|
||||
throw Error("apiSetXFTPConfig bad response: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable))
|
||||
|
||||
suspend fun apiExportArchive(config: ArchiveConfig) {
|
||||
@@ -1693,7 +1685,7 @@ object ChatController {
|
||||
chatModel.networkStatuses[s.agentConnId] = s.networkStatus
|
||||
}
|
||||
}
|
||||
is CR.NewChatItem -> withBGApi {
|
||||
is CR.NewChatItem -> {
|
||||
val cInfo = r.chatItem.chatInfo
|
||||
val cItem = r.chatItem.chatItem
|
||||
if (active(r.user)) {
|
||||
@@ -1708,7 +1700,7 @@ object ChatController {
|
||||
((mc is MsgContent.MCImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV)
|
||||
|| (mc is MsgContent.MCVideo && file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV)
|
||||
|| (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) {
|
||||
receiveFile(rhId, r.user, file.fileId, auto = true)
|
||||
withBGApi { receiveFile(rhId, r.user, file.fileId, auto = true) }
|
||||
}
|
||||
if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) {
|
||||
ntfManager.notifyMessageReceived(r.user, cInfo, cItem)
|
||||
@@ -2167,6 +2159,10 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
fun getXFTPCfg(): XFTPFileConfig {
|
||||
return XFTPFileConfig(minFileSize = 0)
|
||||
}
|
||||
|
||||
fun getNetCfg(): NetCfg {
|
||||
val useSocksProxy = appPrefs.networkUseSocksProxy.get()
|
||||
val proxyHostPort = appPrefs.networkProxyHostPort.get()
|
||||
@@ -2275,6 +2271,7 @@ sealed class CC {
|
||||
class SetTempFolder(val tempFolder: String): CC()
|
||||
class SetFilesFolder(val filesFolder: String): CC()
|
||||
class SetRemoteHostsFolder(val remoteHostsFolder: String): CC()
|
||||
class ApiSetXFTPConfig(val config: XFTPFileConfig?): CC()
|
||||
class ApiSetEncryptLocalFiles(val enable: Boolean): CC()
|
||||
class ApiExportArchive(val config: ArchiveConfig): CC()
|
||||
class ApiImportArchive(val config: ArchiveConfig): CC()
|
||||
@@ -2404,6 +2401,7 @@ sealed class CC {
|
||||
is SetTempFolder -> "/_temp_folder $tempFolder"
|
||||
is SetFilesFolder -> "/_files_folder $filesFolder"
|
||||
is SetRemoteHostsFolder -> "/remote_hosts_folder $remoteHostsFolder"
|
||||
is ApiSetXFTPConfig -> if (config != null) "/_xftp on ${json.encodeToString(config)}" else "/_xftp off"
|
||||
is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}"
|
||||
is ApiExportArchive -> "/_db export ${json.encodeToString(config)}"
|
||||
is ApiImportArchive -> "/_db import ${json.encodeToString(config)}"
|
||||
@@ -2538,6 +2536,7 @@ sealed class CC {
|
||||
is SetTempFolder -> "setTempFolder"
|
||||
is SetFilesFolder -> "setFilesFolder"
|
||||
is SetRemoteHostsFolder -> "setRemoteHostsFolder"
|
||||
is ApiSetXFTPConfig -> "apiSetXFTPConfig"
|
||||
is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles"
|
||||
is ApiExportArchive -> "apiExportArchive"
|
||||
is ApiImportArchive -> "apiImportArchive"
|
||||
@@ -2703,6 +2702,9 @@ sealed class ChatPagination {
|
||||
@Serializable
|
||||
class ComposedMessage(val fileSource: CryptoFile?, val quotedItemId: Long?, val msgContent: MsgContent)
|
||||
|
||||
@Serializable
|
||||
class XFTPFileConfig(val minFileSize: Long)
|
||||
|
||||
@Serializable
|
||||
class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? = null, val parentTempDirectory: String? = null)
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
||||
if (appPlatform.isDesktop) {
|
||||
controller.apiSetRemoteHostsFolder(remoteHostsDir.absolutePath)
|
||||
}
|
||||
controller.apiSetXFTPConfig(controller.getXFTPCfg())
|
||||
controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.get())
|
||||
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
||||
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
|
||||
|
||||
@@ -267,7 +267,7 @@ fun ComposeView(
|
||||
fun loadLinkPreview(url: String, wait: Long? = null) {
|
||||
if (pendingLinkUrl.value == url) {
|
||||
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null))
|
||||
withLongRunningApi(slow = 60_000) {
|
||||
withLongRunningApi(slow = 30_000, deadlock = 60_000) {
|
||||
if (wait != null) delay(wait)
|
||||
val lp = getLinkPreview(url)
|
||||
if (lp != null && pendingLinkUrl.value == url) {
|
||||
@@ -551,7 +551,7 @@ fun ComposeView(
|
||||
}
|
||||
|
||||
fun sendMessage(ttl: Int?) {
|
||||
withLongRunningApi(slow = 120_000) {
|
||||
withLongRunningApi(slow = 30_000, deadlock = 60_000) {
|
||||
sendMessageAsync(null, false, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
|
||||
},
|
||||
inviteMembers = {
|
||||
allowModifyMembers = false
|
||||
withLongRunningApi(slow = 120_000) {
|
||||
withLongRunningApi(slow = 30_000, deadlock = 120_000) {
|
||||
for (contactId in selectedContacts) {
|
||||
val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value)
|
||||
if (member != null) {
|
||||
|
||||
@@ -152,7 +152,7 @@ fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
|
||||
text = generalGetString(MR.strings.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved),
|
||||
confirmText = generalGetString(MR.strings.leave_group_button),
|
||||
onConfirm = {
|
||||
withLongRunningApi(60_000) {
|
||||
withBGApi {
|
||||
chatModel.controller.leaveGroup(rhId, groupInfo.groupId)
|
||||
close?.invoke()
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ fun CIFileView(
|
||||
FileProtocol.LOCAL -> {}
|
||||
}
|
||||
file.fileStatus is CIFileStatus.RcvComplete || (file.fileStatus is CIFileStatus.SndStored && file.fileProtocol == FileProtocol.LOCAL) -> {
|
||||
withLongRunningApi(slow = 600_000) {
|
||||
withLongRunningApi(slow = 60_000, deadlock = 600_000) {
|
||||
var filePath = getLoadedFilePath(file)
|
||||
if (chatModel.connectedToRemote() && filePath == null) {
|
||||
file.loadRemoteFile(true)
|
||||
|
||||
@@ -41,7 +41,7 @@ fun CIVideoView(
|
||||
val filePath = remember(file, CIFile.cachedRemoteFileRequests.toList()) { mutableStateOf(getLoadedFilePath(file)) }
|
||||
if (chatModel.connectedToRemote()) {
|
||||
LaunchedEffect(file) {
|
||||
withLongRunningApi(slow = 600_000) {
|
||||
withLongRunningApi(slow = 60_000, deadlock = 600_000) {
|
||||
if (file != null && file.loaded && getLoadedFilePath(file) == null) {
|
||||
file.loadRemoteFile(false)
|
||||
filePath.value = getLoadedFilePath(file)
|
||||
|
||||
@@ -213,7 +213,7 @@ fun ChatItemView(
|
||||
showMenu.value = false
|
||||
}
|
||||
if (chatModel.connectedToRemote() && fileSource == null) {
|
||||
withLongRunningApi(slow = 600_000) {
|
||||
withLongRunningApi(slow = 60_000, deadlock = 600_000) {
|
||||
cItem.file?.loadRemoteFile(true)
|
||||
fileSource = getLoadedFileSource(cItem.file)
|
||||
shareIfExists()
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -23,7 +22,6 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -191,7 +189,6 @@ class AlertManager {
|
||||
title: String, text: String? = null,
|
||||
confirmText: String = generalGetString(MR.strings.ok),
|
||||
hostDevice: Pair<Long?, String>? = null,
|
||||
shareText: Boolean? = null
|
||||
) {
|
||||
showAlert {
|
||||
AlertDialog(
|
||||
@@ -205,19 +202,10 @@ class AlertManager {
|
||||
delay(200)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
// Can pass shareText = false to prevent showing Share button if it's needed in a specific case
|
||||
val showShareButton = text != null && (shareText == true || (shareText == null && text.length > 500))
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalArrangement = if (showShareButton) Arrangement.SpaceBetween else Arrangement.Center
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
if (showShareButton && text != null) {
|
||||
TextButton(onClick = {
|
||||
clipboard.shareText(text)
|
||||
hideAlert()
|
||||
}) { Text(stringResource(MR.strings.share_verb)) }
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
hideAlert()
|
||||
|
||||
@@ -16,7 +16,7 @@ class ProcessedErrors <T: AgentErrorType>(val interval: Long) {
|
||||
|
||||
fun newError(error: T, offerRestart: Boolean) {
|
||||
timer.cancel()
|
||||
timer = withLongRunningApi(slow = 130_000) {
|
||||
timer = withLongRunningApi(slow = 70_000, deadlock = 130_000) {
|
||||
val delayBeforeNext = (lastShownTimestamp + interval) - System.currentTimeMillis()
|
||||
if ((lastShownOfferRestart || !offerRestart) && delayBeforeNext >= 0) {
|
||||
delay(delayBeforeNext)
|
||||
|
||||
@@ -37,22 +37,30 @@ fun withBGApi(action: suspend CoroutineScope.() -> Unit): Job =
|
||||
CoroutineScope(singleThreadDispatcher).launch(block = { wrapWithLogging(action, it) })
|
||||
}
|
||||
|
||||
fun withLongRunningApi(slow: Long = Long.MAX_VALUE, action: suspend CoroutineScope.() -> Unit): Job =
|
||||
fun withLongRunningApi(slow: Long = Long.MAX_VALUE, deadlock: Long = Long.MAX_VALUE, action: suspend CoroutineScope.() -> Unit): Job =
|
||||
Exception().let {
|
||||
CoroutineScope(Dispatchers.Default).launch(block = { wrapWithLogging(action, it, slow = slow) })
|
||||
CoroutineScope(Dispatchers.Default).launch(block = { wrapWithLogging(action, it, slow = slow, deadlock = deadlock) })
|
||||
}
|
||||
|
||||
private suspend fun wrapWithLogging(action: suspend CoroutineScope.() -> Unit, exception: java.lang.Exception, slow: Long = 20_000) = coroutineScope {
|
||||
private suspend fun wrapWithLogging(action: suspend CoroutineScope.() -> Unit, exception: java.lang.Exception, slow: Long = 10_000, deadlock: Long = 60_000) = coroutineScope {
|
||||
val start = System.currentTimeMillis()
|
||||
val job = launch {
|
||||
delay(deadlock)
|
||||
Log.e(TAG, "Possible deadlock of the thread, not finished after ${deadlock / 1000}s:\n${exception.stackTraceToString()}")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.possible_deadlock_title),
|
||||
text = generalGetString(MR.strings.possible_deadlock_desc).format(deadlock / 1000, exception.stackTraceToString()),
|
||||
)
|
||||
}
|
||||
action()
|
||||
val end = System.currentTimeMillis()
|
||||
if (end - start > slow) {
|
||||
Log.e(TAG, "Possible problem with execution of the thread, took ${(end - start) / 1000}s:\n${exception.stackTraceToString()}")
|
||||
if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) {
|
||||
job.cancel()
|
||||
if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) {
|
||||
val end = System.currentTimeMillis()
|
||||
if (end - start > slow) {
|
||||
Log.e(TAG, "Possible problem with execution of the thread, took ${(end - start) / 1000}s:\n${exception.stackTraceToString()}")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.possible_slow_function_title),
|
||||
text = generalGetString(MR.strings.possible_slow_function_desc).format((end - start) / 1000, exception.stackTraceToString()),
|
||||
shareText = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ fun PrivacySettingsView(
|
||||
val currentUser = chatModel.currentUser.value
|
||||
if (currentUser != null) {
|
||||
fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) {
|
||||
withLongRunningApi(slow = 60_000) {
|
||||
withLongRunningApi(slow = 30_000, deadlock = 60_000) {
|
||||
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
|
||||
chatModel.controller.apiSetUserContactReceipts(currentUser, mrs)
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
@@ -119,7 +119,7 @@ fun PrivacySettingsView(
|
||||
}
|
||||
|
||||
fun setSendReceiptsGroups(enable: Boolean, clearOverrides: Boolean) {
|
||||
withLongRunningApi(slow = 60_000) {
|
||||
withLongRunningApi(slow = 30_000, deadlock = 60_000) {
|
||||
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
|
||||
chatModel.controller.apiSetUserGroupReceipts(currentUser, mrs)
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
|
||||
@@ -1588,6 +1588,8 @@
|
||||
<string name="remote_ctrl_error_busy">سطح المكتب مشغول</string>
|
||||
<string name="remote_ctrl_error_bad_version">يحتوي سطح المكتب على إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين</string>
|
||||
<string name="past_member_vName">العضو السابق %1$s</string>
|
||||
<string name="possible_deadlock_title">مأزق</string>
|
||||
<string name="possible_deadlock_desc">يستغرق تنفيذ التعليمات البرمجية وقتًا طويلاً جدًا: %1$d ثانية. من المحتمل أن التطبيق مجمّد: %2$s</string>
|
||||
<string name="possible_slow_function_title">وظيفة بطيئة</string>
|
||||
<string name="developer_options_section">خيارات المطور</string>
|
||||
<string name="profile_update_event_member_name_changed">تغيّر العضو %1$s إلى %2$s</string>
|
||||
|
||||
@@ -147,6 +147,8 @@
|
||||
<string name="smp_server_test_delete_file">Delete file</string>
|
||||
<string name="error_deleting_user">Error deleting user profile</string>
|
||||
<string name="error_updating_user_privacy">Error updating user privacy</string>
|
||||
<string name="possible_deadlock_title">Deadlock</string>
|
||||
<string name="possible_deadlock_desc">Execution of code takes too long time: %1$d seconds. Probably, the app is frozen: %2$s</string>
|
||||
<string name="possible_slow_function_title">Slow function</string>
|
||||
<string name="possible_slow_function_desc">Execution of function takes too long time: %1$d seconds: %2$s</string>
|
||||
|
||||
|
||||
@@ -1555,6 +1555,7 @@
|
||||
<string name="chat_is_stopped_you_should_transfer_database">Чатът е спрян. Ако вече сте използвали тази база данни на друго устройство, трябва да я прехвърлите обратно, преди да стартирате чата отново.</string>
|
||||
<string name="remote_ctrl_error_bad_invitation">Настолното устройство има грешен код за връзка</string>
|
||||
<string name="remote_ctrl_error_bad_version">Настолното устройство е с неподдържана версия. Моля, уверете се, че използвате една и съща версия и на двете устройства</string>
|
||||
<string name="possible_deadlock_desc">Изпълнението на кода отнема твърде много време: %1$d секунди. Вероятно приложението е замразено: %2$s</string>
|
||||
<string name="possible_slow_function_title">Бавна функция</string>
|
||||
<string name="possible_slow_function_desc">Изпълнението на функцията отнема твърде много време: %1$d секунди: %2$s</string>
|
||||
<string name="show_internal_errors">Покажи вътрешните грешки</string>
|
||||
@@ -1590,4 +1591,5 @@
|
||||
\nПрепоръчително е да рестартирате приложението.</string>
|
||||
<string name="developer_options_section">Опции за разработчици</string>
|
||||
<string name="show_slow_api_calls">Показване на бавни API заявки</string>
|
||||
<string name="possible_deadlock_title">Грешка в заключено положение</string>
|
||||
</resources>
|
||||
@@ -1672,7 +1672,9 @@
|
||||
<string name="possible_slow_function_title">Langsame Funktion</string>
|
||||
<string name="show_slow_api_calls">Zeige langsame API-Aufrufe an</string>
|
||||
<string name="group_member_status_unknown_short">unbekannt</string>
|
||||
<string name="possible_deadlock_title">Blockade</string>
|
||||
<string name="developer_options_section">Optionen für Entwickler</string>
|
||||
<string name="possible_deadlock_desc">Die Code-Ausführung dauert zu lange: %1$d Sekunden. Wahrscheinlich ist die App eingefroren: %2$s</string>
|
||||
<string name="group_member_status_unknown">unbekannter Gruppenmitglieds-Status</string>
|
||||
<string name="v5_5_private_notes_descr">Mit verschlüsselten Dateien und Medien.</string>
|
||||
<string name="v5_5_private_notes">Private Notizen</string>
|
||||
|
||||
@@ -1559,9 +1559,11 @@
|
||||
<string name="remote_host_error_bad_state"><![CDATA[État médiocre de la connexion au mobile <b>%s</b>.]]></string>
|
||||
<string name="remote_ctrl_was_disconnected_title">Connexion interrompue</string>
|
||||
<string name="remote_ctrl_error_bad_state">État médiocre de la connexion avec le bureau</string>
|
||||
<string name="possible_deadlock_title">Impasse</string>
|
||||
<string name="remote_ctrl_error_bad_version">La version de l\'ordinateur de bureau n\'est pas prise en charge. Veillez à utiliser la même version sur les deux appareils.</string>
|
||||
<string name="remote_ctrl_error_disconnected">Le bureau a été déconnecté</string>
|
||||
<string name="developer_options_section">Options pour les développeurs</string>
|
||||
<string name="possible_deadlock_desc">Le code prend trop de temps à s\'exécuter : %1$d secondes. Il est probable que l\'application soit figée : %2$s</string>
|
||||
<string name="agent_internal_error_title">Erreur interne</string>
|
||||
<string name="remote_host_error_bad_version"><![CDATA[La version du mobile <b>%s</b> n\'est pas prise en charge. Veillez à utiliser la même version sur les deux appareils.]]></string>
|
||||
<string name="show_internal_errors">Afficher les erreurs internes</string>
|
||||
|
||||
@@ -1583,7 +1583,9 @@
|
||||
<string name="possible_slow_function_title">Lassú funkció</string>
|
||||
<string name="show_slow_api_calls">Lassú API-hívások megjelenítése</string>
|
||||
<string name="remote_host_error_inactive"><![CDATA[A(z) <b>%s</b> mobil eszköz inaktív]]></string>
|
||||
<string name="possible_deadlock_title">Elakadt</string>
|
||||
<string name="developer_options_section">Fejlesztői beállítások</string>
|
||||
<string name="possible_deadlock_desc">A kód végrehajtása túl sokáig tart: %1$d másodperc. Valószínűleg az alkalmazás lefagyott: %2$s</string>
|
||||
<string name="possible_slow_function_desc">A funkció végrehajtása túl sokáig tart: %1$d másodperc: %2$s</string>
|
||||
<string name="remote_host_error_busy"><![CDATA[A(z) <b>%s</b> mobil eszköz elfoglalt]]></string>
|
||||
<string name="past_member_vName">Legutóbbi tag %1$s</string>
|
||||
|
||||
@@ -1591,7 +1591,9 @@
|
||||
<string name="possible_slow_function_title">Funzione lenta</string>
|
||||
<string name="show_slow_api_calls">Mostra chiamate API lente</string>
|
||||
<string name="group_member_status_unknown_short">sconosciuto</string>
|
||||
<string name="possible_deadlock_desc">L\'esecuzione del codice impiega troppo tempo: %1$d secondi. Probabilmente l\'app è congelata: %2$s</string>
|
||||
<string name="group_member_status_unknown">stato sconosciuto</string>
|
||||
<string name="possible_deadlock_title">Stallo</string>
|
||||
<string name="developer_options_section">Opzioni sviluppatore</string>
|
||||
<string name="v5_5_private_notes">Note private</string>
|
||||
<string name="v5_5_new_interface_languages">Interfaccia in ungherese e turco</string>
|
||||
|
||||
@@ -1571,7 +1571,9 @@
|
||||
<string name="remote_ctrl_error_busy">PC版が処理中</string>
|
||||
<string name="remote_ctrl_error_disconnected">PC版が切断されました</string>
|
||||
<string name="remote_ctrl_error_bad_version">ご利用のPC版のバージョンがサポートされてません。両端末が同じバージョンかどうか、ご確認ください。</string>
|
||||
<string name="possible_deadlock_title">デッドロック状態</string>
|
||||
<string name="developer_options_section">開発者向けの設定</string>
|
||||
<string name="possible_deadlock_desc">処理時間が異常にかかるようです: %1$d 秒。アプリが固まった恐れがあります: %2$s</string>
|
||||
<string name="remote_host_error_busy"><![CDATA[携帯版 <b>%s</b> がただいま処理中]]></string>
|
||||
<string name="possible_slow_function_desc">機能の処理時間が以上にかかってます: %1$d 秒: %2$s</string>
|
||||
<string name="show_internal_errors">内部エラーを表示</string>
|
||||
|
||||
@@ -1574,6 +1574,7 @@
|
||||
<string name="remote_host_error_missing"><![CDATA[Mobiel <b>%s</b> ontbreekt]]></string>
|
||||
<string name="remote_host_error_bad_state"><![CDATA[De verbinding met de mobiel <b>%s</b> is in slechte staat]]></string>
|
||||
<string name="remote_ctrl_error_disconnected">De verbinding met desktop is verbroken</string>
|
||||
<string name="possible_deadlock_title">Impasse</string>
|
||||
<string name="possible_slow_function_desc">Uitvoering van functie duurt te lang: %1$d seconden: %2$s</string>
|
||||
<string name="possible_slow_function_title">Langzame functie</string>
|
||||
<string name="developer_options_section">Ontwikkelaars opties</string>
|
||||
@@ -1587,6 +1588,7 @@
|
||||
<string name="restart_chat_button">Chat opnieuw starten</string>
|
||||
<string name="remote_host_error_timeout"><![CDATA[Time-out bereikt tijdens het verbinden met de mobiel <b>%s</b>]]></string>
|
||||
<string name="remote_ctrl_error_bad_state">De verbinding met de desktop is in slechte staat</string>
|
||||
<string name="possible_deadlock_desc">Het uitvoeren van de code duurt te lang: %1$d seconden. Waarschijnlijk is de app vastgelopen: %2$s</string>
|
||||
<string name="remote_ctrl_error_bad_invitation">Desktop heeft verkeerde uitnodigingscode</string>
|
||||
<string name="remote_host_error_bad_version"><![CDATA[Mobiel <b>%s</b> heeft een niet-ondersteunde versie. Zorg ervoor dat u op beide apparaten dezelfde versie gebruikt]]></string>
|
||||
<string name="remote_ctrl_error_timeout">Time-out bereikt tijdens het verbinden met de desktop</string>
|
||||
|
||||
@@ -1606,6 +1606,7 @@
|
||||
<string name="remote_ctrl_error_bad_version">Komputer ma niewspieraną wersję. Proszę upewnić się, że używasz tych samych wersji na obu urządzeniach</string>
|
||||
<string name="blocked_by_admin_items_description">%d wiadomości zablokowanych przez admina</string>
|
||||
<string name="error_creating_message">Błąd tworzenia wiadomości</string>
|
||||
<string name="possible_deadlock_desc">Wykonanie kodu zajmuje za dużo czasu: %1$d sekund. Prawdopodobnie aplikacja jest zamrożona: %2$s</string>
|
||||
<string name="possible_slow_function_desc">Wykonanie kodu zajmuje za dużo czasu: %1$d sekund: %2$s</string>
|
||||
<string name="note_folder_local_display_name">Prywatne notatki</string>
|
||||
<string name="group_member_status_unknown">nieznany status</string>
|
||||
@@ -1620,6 +1621,7 @@
|
||||
<string name="remote_host_error_inactive"><![CDATA[Telefon <b>%s</b> jest nieaktywny]]></string>
|
||||
<string name="remote_host_error_bad_version"><![CDATA[Telefon <b>%s</b> ma niewspieraną wersję. Proszę, upewnij się, że używasz tej samej wersji na obydwu urządzeniach]]></string>
|
||||
<string name="group_member_status_unknown_short">nieznany</string>
|
||||
<string name="possible_deadlock_title">Blokada</string>
|
||||
<string name="profile_update_event_contact_name_changed">kontakt %1$s zmieniony na %2$s</string>
|
||||
<string name="profile_update_event_removed_address">usunięto adres kontaktu</string>
|
||||
<string name="profile_update_event_removed_picture">usunięto zdjęcie profilu</string>
|
||||
|
||||
@@ -1680,6 +1680,8 @@
|
||||
<string name="error_showing_message">ошибка отображения сообщения</string>
|
||||
<string name="error_showing_content">ошибка отображения содержания</string>
|
||||
<string name="remote_ctrl_disconnected_with_reason">Отсоединён по причине: %s</string>
|
||||
<string name="possible_deadlock_title">Взаимная блокировка</string>
|
||||
<string name="possible_deadlock_desc">Выполнение задачи занимает долгое время: %1$d секунд. Возможно, приложение заблокировано: %2$s</string>
|
||||
<string name="possible_slow_function_desc">Выполнение задачи занимает долгое время: %1$d секунд: %2$s</string>
|
||||
<string name="possible_slow_function_title">Медленный вызов</string>
|
||||
<string name="profile_update_event_contact_name_changed">контакт %1$s изменён на %2$s</string>
|
||||
|
||||
@@ -1586,6 +1586,8 @@
|
||||
<string name="remote_host_error_bad_state"><![CDATA[到移动主机 <b>%s</b>的连接状态不佳]]></string>
|
||||
<string name="remote_host_error_timeout"><![CDATA[连接到移动主机<b>%s</b>时超时]]></string>
|
||||
<string name="failed_to_create_user_invalid_desc">显示名无效。请另选一个名称。</string>
|
||||
<string name="possible_deadlock_title">死锁</string>
|
||||
<string name="possible_deadlock_desc">代码执行花费的时间过久:%1$d秒。应用可能卡住了:%2$s</string>
|
||||
<string name="possible_slow_function_title">慢函数</string>
|
||||
<string name="show_slow_api_calls">显示缓慢的 API 调用</string>
|
||||
<string name="past_member_vName">过往成员 %1$s</string>
|
||||
|
||||
@@ -39,8 +39,7 @@ fun showApp() {
|
||||
WindowExceptionHandler { e ->
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.app_was_crashed),
|
||||
text = e.stackTraceToString(),
|
||||
shareText = true
|
||||
text = e.stackTraceToString()
|
||||
)
|
||||
Log.e(TAG, "App crashed, thread name: " + Thread.currentThread().name + ", exception: " + e.stackTraceToString())
|
||||
window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING))
|
||||
|
||||
@@ -42,7 +42,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
|
||||
}
|
||||
var fileSource = getLoadedFileSource(cItem.file)
|
||||
if (chatModel.connectedToRemote() && fileSource == null) {
|
||||
withLongRunningApi(slow = 600_000) {
|
||||
withLongRunningApi(slow = 60_000, deadlock = 600_000) {
|
||||
cItem.file?.loadRemoteFile(true)
|
||||
fileSource = getLoadedFileSource(cItem.file)
|
||||
saveIfExists()
|
||||
@@ -51,7 +51,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
|
||||
})
|
||||
}
|
||||
|
||||
actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) = withLongRunningApi(slow = 600_000) {
|
||||
actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) = withLongRunningApi(slow = 60_000, deadlock = 600_000) {
|
||||
var fileSource = getLoadedFileSource(cItem.file)
|
||||
if (chatModel.connectedToRemote() && fileSource == null) {
|
||||
cItem.file?.loadRemoteFile(true)
|
||||
|
||||
@@ -25,11 +25,11 @@ android.nonTransitiveRClass=true
|
||||
android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
|
||||
android.version_name=5.5.5
|
||||
android.version_code=185
|
||||
android.version_name=5.5.2
|
||||
android.version_code=179
|
||||
|
||||
desktop.version_name=5.5.5
|
||||
desktop.version_code=31
|
||||
desktop.version_name=5.5.2
|
||||
desktop.version_code=28
|
||||
|
||||
kotlin.version=1.8.20
|
||||
gradle.plugin.version=7.4.2
|
||||
|
||||
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 32c94df040b7921584a4685a814818daec3bf209
|
||||
tag: a516c2f72c81bb4a433c4065b1b5aa484b8292b1
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: Download SimpleX apps
|
||||
permalink: /downloads/index.html
|
||||
revision: 11.02.2024
|
||||
revision: 25.11.2023
|
||||
---
|
||||
|
||||
| Updated 11.02.2024 | Languages: EN |
|
||||
| Updated 25.11.2023 | Languages: EN |
|
||||
# Download SimpleX apps
|
||||
|
||||
The latest stable version is v5.5.3.
|
||||
The latest stable version is v5.5.
|
||||
|
||||
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
|
||||
|
||||
@@ -21,24 +21,24 @@ You can get the latest beta releases from [GitHub](https://github.com/simplex-ch
|
||||
|
||||
Using the same profile as on mobile device is not yet supported – you need to create a separate profile to use desktop apps.
|
||||
|
||||
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-ubuntu-22_04-x86_64.deb).
|
||||
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-desktop-ubuntu-22_04-x86_64.deb).
|
||||
|
||||
**Mac**: [aarch64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-macos-aarch64.dmg) (Apple Silicon), [x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-macos-x86_64.dmg) (Intel).
|
||||
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
|
||||
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-windows-x86_64.msi).
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-desktop-windows-x86_64.msi).
|
||||
|
||||
## Mobile apps
|
||||
|
||||
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084), [TestFlight](https://testflight.apple.com/join/DWuT2LQu).
|
||||
|
||||
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-armv7a.apk).
|
||||
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-armv7a.apk).
|
||||
|
||||
## Terminal (console) app
|
||||
|
||||
See [Using terminal app](/docs/CLI.md).
|
||||
|
||||
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-chat-ubuntu-22_04-x86-64).
|
||||
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-chat-ubuntu-22_04-x86-64).
|
||||
|
||||
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-chat-macos-x86-64), aarch64 - [compile from source](/docs/CLI.md#).
|
||||
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-chat-macos-x86-64), aarch64 - [compile from source](/docs/CLI.md#).
|
||||
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-chat-windows-x86-64).
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.5.0/simplex-chat-windows-x86-64).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 5.5.5.0
|
||||
version: 5.5.2.0
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
||||
@@ -12,6 +12,7 @@ export type ChatCommand =
|
||||
| APIStopChat
|
||||
| SetTempFolder
|
||||
| SetFilesFolder
|
||||
| APISetXFTPConfig
|
||||
| SetIncognito
|
||||
| APIExportArchive
|
||||
| APIImportArchive
|
||||
@@ -111,6 +112,7 @@ type ChatCommandTag =
|
||||
| "apiStopChat"
|
||||
| "setTempFolder"
|
||||
| "setFilesFolder"
|
||||
| "apiSetXFTPConfig"
|
||||
| "setIncognito"
|
||||
| "apiExportArchive"
|
||||
| "apiImportArchive"
|
||||
@@ -240,6 +242,15 @@ export interface SetFilesFolder extends IChatCommand {
|
||||
filePath: string
|
||||
}
|
||||
|
||||
export interface APISetXFTPConfig extends IChatCommand {
|
||||
type: "apiSetXFTPConfig"
|
||||
config?: XFTPFileConfig
|
||||
}
|
||||
|
||||
export interface XFTPFileConfig {
|
||||
minFileSize: number
|
||||
}
|
||||
|
||||
export interface SetIncognito extends IChatCommand {
|
||||
type: "setIncognito"
|
||||
incognito: boolean
|
||||
@@ -696,6 +707,8 @@ export function cmdString(cmd: ChatCommand): string {
|
||||
return `/_temp_folder ${cmd.tempFolder}`
|
||||
case "setFilesFolder":
|
||||
return `/_files_folder ${cmd.filePath}`
|
||||
case "apiSetXFTPConfig":
|
||||
return `/_xftp ${onOff(cmd.config)}${maybeJSON(cmd.config)}`
|
||||
case "setIncognito":
|
||||
return `/incognito ${onOff(cmd.incognito)}`
|
||||
case "apiExportArchive":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."32c94df040b7921584a4685a814818daec3bf209" = "0bfyzra8x67zwqr7g8hkglxpy503qwn0xni0sjnbjmvh7wlh6pyz";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."a516c2f72c81bb4a433c4065b1b5aa484b8292b1" = "05ny2i262c236li5w040i1nd3l037cpzgbzjknlla9dd139f3al3";
|
||||
"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";
|
||||
|
||||
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 5.5.5.0
|
||||
version: 5.5.2.0
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
||||
@@ -81,7 +81,7 @@ import Simplex.Chat.Types.Util
|
||||
import Simplex.Chat.Util (encryptFile, shuffle)
|
||||
import Simplex.FileTransfer.Client.Main (maxFileSize)
|
||||
import Simplex.FileTransfer.Client.Presets (defaultXFTPServers)
|
||||
import Simplex.FileTransfer.Description (ValidFileDescription)
|
||||
import Simplex.FileTransfer.Description (ValidFileDescription, gb, kb, mb)
|
||||
import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI)
|
||||
import Simplex.Messaging.Agent as Agent
|
||||
import Simplex.Messaging.Agent.Client (AgentStatsKey (..), SubInfo (..), agentClientStore, getAgentWorkersDetails, getAgentWorkersSummary, temporaryAgentError)
|
||||
@@ -142,6 +142,8 @@ defaultChatConfig =
|
||||
xftpDescrPartSize = 14000,
|
||||
inlineFiles = defaultInlineFilesConfig,
|
||||
autoAcceptFileSize = 0,
|
||||
xftpFileConfig = Just defaultXFTPFileConfig,
|
||||
tempDir = Nothing,
|
||||
showReactions = False,
|
||||
showReceipts = False,
|
||||
logLevel = CLLImportant,
|
||||
@@ -199,7 +201,7 @@ newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Boo
|
||||
newChatController
|
||||
ChatDatabase {chatStore, agentStore}
|
||||
user
|
||||
cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, deviceNameForRemote}
|
||||
cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, tempDir, deviceNameForRemote}
|
||||
ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, networkConfig, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, deviceName, optFilesFolder, showReactions, allowInstantFiles, autoAcceptFileSize}
|
||||
backgroundMode = do
|
||||
let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False}
|
||||
@@ -234,7 +236,8 @@ newChatController
|
||||
chatActivated <- newTVarIO True
|
||||
showLiveItems <- newTVarIO False
|
||||
encryptLocalFiles <- newTVarIO False
|
||||
tempDirectory <- newTVarIO Nothing
|
||||
userXFTPFileConfig <- newTVarIO $ xftpFileConfig cfg
|
||||
tempDirectory <- newTVarIO tempDir
|
||||
contactMergeEnabled <- newTVarIO True
|
||||
pure
|
||||
ChatController
|
||||
@@ -269,6 +272,7 @@ newChatController
|
||||
chatActivated,
|
||||
showLiveItems,
|
||||
encryptLocalFiles,
|
||||
userXFTPFileConfig,
|
||||
tempDirectory,
|
||||
logFilePath = logFile,
|
||||
contactMergeEnabled
|
||||
@@ -578,6 +582,9 @@ processChatCommand' vr = \case
|
||||
createDirectoryIfMissing True rf
|
||||
chatWriteVar remoteHostsFolder $ Just rf
|
||||
ok_
|
||||
APISetXFTPConfig cfg -> do
|
||||
asks userXFTPFileConfig >>= atomically . (`writeTVar` cfg)
|
||||
ok_
|
||||
APISetEncryptLocalFiles on -> chatWriteVar encryptLocalFiles on >> ok_
|
||||
SetContactMergeEnabled onOff -> do
|
||||
asks contactMergeEnabled >>= atomically . (`writeTVar` onOff)
|
||||
@@ -638,7 +645,7 @@ processChatCommand' vr = \case
|
||||
memStatuses -> pure $ Just $ map (uncurry MemberDeliveryStatus) memStatuses
|
||||
_ -> pure Nothing
|
||||
pure $ CRChatItemInfo user aci ChatItemInfo {itemVersions, memberDeliveryStatuses}
|
||||
APISendMessage (ChatRef cType chatId) live itemTTL (ComposedMessage file_ quotedItemId_ mc) -> withUser $ \user -> withChatLock "sendMessage" $ case cType of
|
||||
APISendMessage (ChatRef cType chatId) live itemTTL (ComposedMessage file_ quotedItemId_ mc) -> withUser $ \user@User {userId} -> withChatLock "sendMessage" $ case cType of
|
||||
CTDirect -> do
|
||||
ct@Contact {contactId, contactUsed} <- withStore $ \db -> getContact db user chatId
|
||||
assertDirectAllowed user MDSnd ct XMsgNew_
|
||||
@@ -646,19 +653,45 @@ processChatCommand' vr = \case
|
||||
if isVoice mc && not (featureAllowed SCFVoice forUser ct)
|
||||
then pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (chatFeatureNameText CFVoice))
|
||||
else do
|
||||
(fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer ct
|
||||
(fInv_, ciFile_, ft_) <- unzipMaybe3 <$> setupSndFileTransfer ct
|
||||
timed_ <- sndContactCITimed live ct itemTTL
|
||||
(msgContainer, quotedItem_) <- prepareMsg fInv_ timed_
|
||||
(msg, _) <- sendDirectContactMessage ct (XMsgNew msgContainer)
|
||||
(msg@SndMessage {sharedMsgId}, _) <- sendDirectContactMessage ct (XMsgNew msgContainer)
|
||||
ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live
|
||||
case ft_ of
|
||||
Just ft@FileTransferMeta {fileInline = Just IFMSent} ->
|
||||
sendDirectFileInline ct ft sharedMsgId
|
||||
_ -> pure ()
|
||||
forM_ (timed_ >>= timedDeleteAt') $
|
||||
startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci)
|
||||
pure $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci)
|
||||
where
|
||||
setupSndFileTransfer :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd))
|
||||
setupSndFileTransfer :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd, FileTransferMeta))
|
||||
setupSndFileTransfer ct = forM file_ $ \file -> do
|
||||
fileSize <- checkSndFile file
|
||||
xftpSndFileTransfer user file fileSize 1 $ CGContact ct
|
||||
(fileSize, fileMode) <- checkSndFile mc file 1
|
||||
case fileMode of
|
||||
SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline
|
||||
SendFileXFTP -> xftpSndFileTransfer user file fileSize 1 $ CGContact ct
|
||||
where
|
||||
smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta)
|
||||
smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled
|
||||
smpSndFileTransfer (CryptoFile file Nothing) fileSize fileInline = do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
(agentConnId_, fileConnReq) <-
|
||||
if isJust fileInline
|
||||
then pure (Nothing, Nothing)
|
||||
else bimap Just Just <$> withAgent (\a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode)
|
||||
let fileName = takeFileName file
|
||||
fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq, fileInline, fileDescr = Nothing}
|
||||
chSize <- asks $ fileChunkSize . config
|
||||
withStore $ \db -> do
|
||||
ft@FileTransferMeta {fileId} <- liftIO $ createSndDirectFileTransfer db userId ct file fileInvitation agentConnId_ chSize subMode
|
||||
fileStatus <- case fileInline of
|
||||
Just IFMSent -> createSndDirectInlineFT db ct ft $> CIFSSndTransfer 0 1
|
||||
_ -> pure CIFSSndStored
|
||||
let fileSource = Just $ CF.plain file
|
||||
ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol = FPSMP}
|
||||
pure (fileInvitation, ciFile, ft)
|
||||
prepareMsg :: Maybe FileInvitation -> Maybe CITimed -> m (MsgContainer, Maybe (CIQuote 'CTDirect))
|
||||
prepareMsg fInv_ timed_ = case quotedItemId_ of
|
||||
Nothing -> pure (MCSimple (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing)
|
||||
@@ -685,27 +718,53 @@ processChatCommand' vr = \case
|
||||
| isVoice mc && not (groupFeatureAllowed SGFVoice gInfo) = notAllowedError GFVoice
|
||||
| not (isVoice mc) && isJust file_ && not (groupFeatureAllowed SGFFiles gInfo) = notAllowedError GFFiles
|
||||
| otherwise = do
|
||||
(fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer g (length $ filter memberCurrent ms)
|
||||
(fInv_, ciFile_, ft_) <- unzipMaybe3 <$> setupSndFileTransfer g (length $ filter memberCurrent ms)
|
||||
timed_ <- sndGroupCITimed live gInfo itemTTL
|
||||
(msgContainer, quotedItem_) <- prepareGroupMsg user gInfo mc quotedItemId_ fInv_ timed_ live
|
||||
(msg, sentToMembers) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer)
|
||||
(msg@SndMessage {sharedMsgId}, sentToMembers) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer)
|
||||
ci <- saveSndChatItem' user (CDGroupSnd gInfo) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live
|
||||
withStore' $ \db ->
|
||||
forM_ sentToMembers $ \GroupMember {groupMemberId} ->
|
||||
createGroupSndStatus db (chatItemId' ci) groupMemberId CISSndNew
|
||||
mapM_ (sendGroupFileInline ms sharedMsgId) ft_
|
||||
forM_ (timed_ >>= timedDeleteAt') $
|
||||
startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci)
|
||||
pure $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci)
|
||||
notAllowedError f = pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText f))
|
||||
setupSndFileTransfer :: Group -> Int -> m (Maybe (FileInvitation, CIFile 'MDSnd))
|
||||
setupSndFileTransfer g n = forM file_ $ \file -> do
|
||||
fileSize <- checkSndFile file
|
||||
xftpSndFileTransfer user file fileSize n $ CGGroup g
|
||||
setupSndFileTransfer :: Group -> Int -> m (Maybe (FileInvitation, CIFile 'MDSnd, FileTransferMeta))
|
||||
setupSndFileTransfer g@(Group gInfo _) n = forM file_ $ \file -> do
|
||||
(fileSize, fileMode) <- checkSndFile mc file $ fromIntegral n
|
||||
case fileMode of
|
||||
SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline
|
||||
SendFileXFTP -> xftpSndFileTransfer user file fileSize n $ CGGroup g
|
||||
where
|
||||
smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta)
|
||||
smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled
|
||||
smpSndFileTransfer (CryptoFile file Nothing) fileSize fileInline = do
|
||||
let fileName = takeFileName file
|
||||
fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq = Nothing, fileInline, fileDescr = Nothing}
|
||||
fileStatus = if fileInline == Just IFMSent then CIFSSndTransfer 0 1 else CIFSSndStored
|
||||
chSize <- asks $ fileChunkSize . config
|
||||
withStore' $ \db -> do
|
||||
ft@FileTransferMeta {fileId} <- createSndGroupFileTransfer db userId gInfo file fileInvitation chSize
|
||||
let fileSource = Just $ CF.plain file
|
||||
ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol = FPSMP}
|
||||
pure (fileInvitation, ciFile, ft)
|
||||
sendGroupFileInline :: [GroupMember] -> SharedMsgId -> FileTransferMeta -> m ()
|
||||
sendGroupFileInline ms sharedMsgId ft@FileTransferMeta {fileInline} =
|
||||
when (fileInline == Just IFMSent) . forM_ ms $ \m ->
|
||||
processMember m `catchChatError` (toView . CRChatError (Just user))
|
||||
where
|
||||
processMember m@GroupMember {activeConn = Just conn@Connection {connStatus}} =
|
||||
when (connStatus == ConnReady || connStatus == ConnSndReady) $ do
|
||||
void . withStore' $ \db -> createSndGroupInlineFT db m conn ft
|
||||
sendMemberFileInline m conn ft sharedMsgId
|
||||
processMember _ = pure ()
|
||||
CTLocal -> pure $ chatCmdError (Just user) "not supported"
|
||||
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
|
||||
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
||||
where
|
||||
xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd)
|
||||
xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta)
|
||||
xftpSndFileTransfer user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup = do
|
||||
let fileName = takeFileName filePath
|
||||
fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False}
|
||||
@@ -729,7 +788,10 @@ processChatCommand' vr = \case
|
||||
withStore' $
|
||||
\db -> createSndFTDescrXFTP db user (Just m) conn ft fileDescr
|
||||
saveMemberFD _ = pure ()
|
||||
pure (fInv, ciFile)
|
||||
pure (fInv, ciFile, ft)
|
||||
unzipMaybe3 :: Maybe (a, b, c) -> (Maybe a, Maybe b, Maybe c)
|
||||
unzipMaybe3 (Just (a, b, c)) = (Just a, Just b, Just c)
|
||||
unzipMaybe3 _ = (Nothing, Nothing, Nothing)
|
||||
APICreateChatItem folderId (ComposedMessage file_ quotedItemId_ mc) -> withUser $ \user -> do
|
||||
forM_ quotedItemId_ $ \_ -> throwError $ ChatError $ CECommandError "not supported"
|
||||
nf <- withStore $ \db -> getNoteFolder db user folderId
|
||||
@@ -947,7 +1009,7 @@ processChatCommand' vr = \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 user ct
|
||||
withStore' $ \db -> deleteContact db user ct
|
||||
pure $ CRContactDeleted user ct
|
||||
CTContactConnection -> withChatLock "deleteChat contactConnection" . procCmd $ do
|
||||
conn@PendingContactConnection {pccAgentConnId = AgentConnId acId} <- withStore $ \db -> getPendingContactConnection db userId chatId
|
||||
@@ -988,7 +1050,7 @@ processChatCommand' vr = \case
|
||||
Just _ -> pure []
|
||||
Nothing -> do
|
||||
conns <- withStore' $ \db -> getContactConnections db userId ct
|
||||
withStore (\db -> setContactDeleted db user ct)
|
||||
withStore' (\db -> setContactDeleted db user ct)
|
||||
`catchChatError` (toView . CRChatError (Just user))
|
||||
pure $ map aConnId conns
|
||||
CTLocal -> pure $ chatCmdError (Just user) "not supported"
|
||||
@@ -2140,13 +2202,27 @@ processChatCommand' vr = \case
|
||||
contactMember Contact {contactId} =
|
||||
find $ \GroupMember {memberContactId = cId, memberStatus = s} ->
|
||||
cId == Just contactId && s /= GSMemRemoved && s /= GSMemLeft
|
||||
checkSndFile :: CryptoFile -> m Integer
|
||||
checkSndFile (CryptoFile f cfArgs) = do
|
||||
checkSndFile :: MsgContent -> CryptoFile -> Integer -> m (Integer, SendFileMode)
|
||||
checkSndFile mc (CryptoFile f cfArgs) n = do
|
||||
fsFilePath <- toFSFilePath f
|
||||
unlessM (doesFileExist fsFilePath) . throwChatError $ CEFileNotFound f
|
||||
ChatConfig {fileChunkSize, inlineFiles} <- asks config
|
||||
xftpCfg <- readTVarIO =<< asks userXFTPFileConfig
|
||||
fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cfArgs
|
||||
when (fromInteger fileSize > maxFileSize) $ throwChatError $ CEFileSize f
|
||||
pure fileSize
|
||||
let chunks = -((-fileSize) `div` fileChunkSize)
|
||||
fileInline = inlineFileMode mc inlineFiles chunks n
|
||||
fileMode = case xftpCfg of
|
||||
Just cfg
|
||||
| isJust cfArgs -> SendFileXFTP
|
||||
| fileInline == Just IFMSent || fileSize < minFileSize cfg || n <= 0 -> SendFileSMP fileInline
|
||||
| otherwise -> SendFileXFTP
|
||||
_ -> SendFileSMP fileInline
|
||||
pure (fileSize, fileMode)
|
||||
inlineFileMode mc InlineFilesConfig {offerChunks, sendChunks, totalSendChunks} chunks n
|
||||
| chunks > offerChunks = Nothing
|
||||
| chunks <= sendChunks && chunks * n <= totalSendChunks && isVoice mc = Just IFMSent
|
||||
| otherwise = Just IFMOffer
|
||||
updateProfile :: User -> Profile -> m ChatResponse
|
||||
updateProfile user p' = updateProfile_ user p' $ withStore $ \db -> updateUserProfile db user p'
|
||||
updateProfile_ :: User -> Profile -> m User -> m ChatResponse
|
||||
@@ -3056,7 +3132,7 @@ cleanupManager = do
|
||||
cleanupDeletedContacts user = do
|
||||
contacts <- withStore' (`getDeletedContacts` user)
|
||||
forM_ contacts $ \ct ->
|
||||
withStore (\db -> deleteContactWithoutGroups db user ct)
|
||||
withStore' (\db -> deleteContactWithoutGroups db user ct)
|
||||
`catchChatError` (toView . CRChatError (Just user))
|
||||
cleanupMessages = do
|
||||
ts <- liftIO getCurrentTime
|
||||
@@ -4836,7 +4912,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
else do
|
||||
contactConns <- withStore' $ \db -> getContactConnections db userId c
|
||||
deleteAgentConnectionsAsync user $ map aConnId contactConns
|
||||
withStore $ \db -> deleteContact db user c
|
||||
withStore' $ \db -> deleteContact db user c
|
||||
where
|
||||
brokerTs = metaBrokerTs msgMeta
|
||||
|
||||
@@ -6419,6 +6495,8 @@ chatCommandP =
|
||||
"/_temp_folder " *> (SetTempFolder <$> filePath),
|
||||
("/_files_folder " <|> "/files_folder ") *> (SetFilesFolder <$> filePath),
|
||||
"/remote_hosts_folder " *> (SetRemoteHostsFolder <$> filePath),
|
||||
"/_xftp " *> (APISetXFTPConfig <$> ("on " *> (Just <$> jsonP) <|> ("off" $> Nothing))),
|
||||
"/xftp " *> (APISetXFTPConfig <$> ("on" *> (Just <$> xftpCfgP) <|> ("off" $> Nothing))),
|
||||
"/_files_encrypt " *> (APISetEncryptLocalFiles <$> onOffP),
|
||||
"/contact_merge " *> (SetContactMergeEnabled <$> onOffP),
|
||||
"/_db export " *> (APIExportArchive <$> jsonP),
|
||||
@@ -6790,6 +6868,14 @@ chatCommandP =
|
||||
logErrors <- " log=" *> onOffP <|> pure False
|
||||
let tcpTimeout = 1000000 * fromMaybe (maybe 5 (const 10) socksProxy) t_
|
||||
pure $ fullNetworkConfig socksProxy tcpTimeout logErrors
|
||||
xftpCfgP = XFTPFileConfig <$> (" size=" *> fileSizeP <|> pure 0)
|
||||
fileSizeP =
|
||||
A.choice
|
||||
[ gb <$> A.decimal <* "gb",
|
||||
mb <$> A.decimal <* "mb",
|
||||
kb <$> A.decimal <* "kb",
|
||||
A.decimal
|
||||
]
|
||||
dbKeyP = nonEmptyKey <$?> strP
|
||||
nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k
|
||||
dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False}
|
||||
|
||||
@@ -128,6 +128,8 @@ data ChatConfig = ChatConfig
|
||||
xftpDescrPartSize :: Int,
|
||||
inlineFiles :: InlineFilesConfig,
|
||||
autoAcceptFileSize :: Integer,
|
||||
xftpFileConfig :: Maybe XFTPFileConfig, -- Nothing - XFTP is disabled
|
||||
tempDir :: Maybe FilePath,
|
||||
showReactions :: Bool,
|
||||
showReceipts :: Bool,
|
||||
subscriptionEvents :: Bool,
|
||||
@@ -202,6 +204,7 @@ data ChatController = ChatController
|
||||
timedItemThreads :: TMap (ChatRef, ChatItemId) (TVar (Maybe (Weak ThreadId))),
|
||||
showLiveItems :: TVar Bool,
|
||||
encryptLocalFiles :: TVar Bool,
|
||||
userXFTPFileConfig :: TVar (Maybe XFTPFileConfig),
|
||||
tempDirectory :: TVar (Maybe FilePath),
|
||||
logFilePath :: Maybe FilePath,
|
||||
contactMergeEnabled :: TVar Bool
|
||||
@@ -239,6 +242,7 @@ data ChatCommand
|
||||
| SetTempFolder FilePath
|
||||
| SetFilesFolder FilePath
|
||||
| SetRemoteHostsFolder FilePath
|
||||
| APISetXFTPConfig (Maybe XFTPFileConfig)
|
||||
| APISetEncryptLocalFiles Bool
|
||||
| SetContactMergeEnabled Bool
|
||||
| APIExportArchive ArchiveConfig
|
||||
@@ -469,6 +473,7 @@ allowRemoteCommand = \case
|
||||
SetTempFolder _ -> False
|
||||
SetFilesFolder _ -> False
|
||||
SetRemoteHostsFolder _ -> False
|
||||
APISetXFTPConfig _ -> False
|
||||
APISetEncryptLocalFiles _ -> False
|
||||
APIExportArchive _ -> False
|
||||
APIImportArchive _ -> False
|
||||
@@ -929,6 +934,14 @@ instance FromJSON ComposedMessage where
|
||||
parseJSON invalid =
|
||||
JT.prependFailure "bad ComposedMessage, " (JT.typeMismatch "Object" invalid)
|
||||
|
||||
data XFTPFileConfig = XFTPFileConfig
|
||||
{ minFileSize :: Integer
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
defaultXFTPFileConfig :: XFTPFileConfig
|
||||
defaultXFTPFileConfig = XFTPFileConfig {minFileSize = 0}
|
||||
|
||||
data NtfMsgInfo = NtfMsgInfo {msgId :: Text, msgTs :: UTCTime}
|
||||
deriving (Show)
|
||||
|
||||
@@ -988,6 +1001,11 @@ data CoreVersionInfo = CoreVersionInfo
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data SendFileMode
|
||||
= SendFileSMP (Maybe InlineFileMode)
|
||||
| SendFileXFTP
|
||||
deriving (Show)
|
||||
|
||||
data SlowSQLQuery = SlowSQLQuery
|
||||
{ query :: Text,
|
||||
queryStats :: SlowQueryStats
|
||||
@@ -1391,4 +1409,6 @@ $(JQ.deriveFromJSON defaultJSON ''ArchiveConfig)
|
||||
|
||||
$(JQ.deriveFromJSON defaultJSON ''DBEncryptionConfig)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''XFTPFileConfig)
|
||||
|
||||
$(JQ.deriveToJSON defaultJSON ''ComposedMessage)
|
||||
|
||||
@@ -229,45 +229,37 @@ 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 -> User -> Contact -> ExceptT StoreError IO ()
|
||||
deleteContact db user@User {userId} ct@Contact {contactId, localDisplayName, activeConn} = do
|
||||
assertNotUser db user ct
|
||||
liftIO $ 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
|
||||
then do
|
||||
deleteContactProfile_ db userId contactId
|
||||
-- user's local display name already checked in assertNotUser
|
||||
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||
else 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_ activeConn $ \Connection {customUserProfileId} ->
|
||||
forM_ customUserProfileId $ \profileId ->
|
||||
deleteUnusedIncognitoProfileById_ db user profileId
|
||||
deleteContact :: DB.Connection -> User -> Contact -> IO ()
|
||||
deleteContact db user@User {userId} Contact {contactId, localDisplayName, activeConn} = 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
|
||||
then do
|
||||
deleteContactProfile_ db userId contactId
|
||||
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||
else 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_ activeConn $ \Connection {customUserProfileId} ->
|
||||
forM_ customUserProfileId $ \profileId ->
|
||||
deleteUnusedIncognitoProfileById_ db user profileId
|
||||
|
||||
-- should only be used if contact is not member of any groups
|
||||
deleteContactWithoutGroups :: DB.Connection -> User -> Contact -> ExceptT StoreError IO ()
|
||||
deleteContactWithoutGroups db user@User {userId} ct@Contact {contactId, localDisplayName, activeConn} = do
|
||||
assertNotUser db user ct
|
||||
liftIO $ do
|
||||
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND contact_id = ?" (userId, contactId)
|
||||
deleteContactProfile_ db userId contactId
|
||||
-- user's local display name already checked in assertNotUser
|
||||
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||
DB.execute db "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?" (userId, contactId)
|
||||
forM_ activeConn $ \Connection {customUserProfileId} ->
|
||||
forM_ customUserProfileId $ \profileId ->
|
||||
deleteUnusedIncognitoProfileById_ db user profileId
|
||||
deleteContactWithoutGroups :: DB.Connection -> User -> Contact -> IO ()
|
||||
deleteContactWithoutGroups db user@User {userId} Contact {contactId, localDisplayName, activeConn} = do
|
||||
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND contact_id = ?" (userId, contactId)
|
||||
deleteContactProfile_ db userId contactId
|
||||
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||
DB.execute db "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?" (userId, contactId)
|
||||
forM_ activeConn $ \Connection {customUserProfileId} ->
|
||||
forM_ customUserProfileId $ \profileId ->
|
||||
deleteUnusedIncognitoProfileById_ db user profileId
|
||||
|
||||
setContactDeleted :: DB.Connection -> User -> Contact -> ExceptT StoreError IO ()
|
||||
setContactDeleted db user@User {userId} ct@Contact {contactId} = do
|
||||
assertNotUser db user ct
|
||||
liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute db "UPDATE contacts SET deleted = 1, updated_at = ? WHERE user_id = ? AND contact_id = ?" (currentTs, userId, contactId)
|
||||
setContactDeleted :: DB.Connection -> User -> Contact -> IO ()
|
||||
setContactDeleted db User {userId} Contact {contactId} = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute db "UPDATE contacts SET deleted = 1, updated_at = ? WHERE user_id = ? AND contact_id = ?" (currentTs, userId, contactId)
|
||||
|
||||
getDeletedContacts :: DB.Connection -> User -> IO [Contact]
|
||||
getDeletedContacts db user@User {userId} = do
|
||||
@@ -328,7 +320,7 @@ updateContactProfile db user@User {userId} c p'
|
||||
ExceptT . withLocalDisplayName db userId newName $ \ldn -> do
|
||||
currentTs <- getCurrentTime
|
||||
updateContactProfile_' db userId profileId p' currentTs
|
||||
updateContactLDN_ db user contactId localDisplayName ldn currentTs
|
||||
updateContactLDN_ db userId contactId localDisplayName ldn currentTs
|
||||
pure $ Right c {localDisplayName = ldn, profile, mergedPreferences}
|
||||
where
|
||||
Contact {contactId, localDisplayName, profile = LocalProfile {profileId, displayName, localAlias}, userPreferences} = c
|
||||
@@ -499,8 +491,8 @@ updateMemberContactProfile_' db userId profileId Profile {displayName, fullName,
|
||||
|]
|
||||
(displayName, fullName, image, updatedAt, userId, profileId)
|
||||
|
||||
updateContactLDN_ :: DB.Connection -> User -> Int64 -> ContactName -> ContactName -> UTCTime -> IO ()
|
||||
updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt = do
|
||||
updateContactLDN_ :: DB.Connection -> UserId -> Int64 -> ContactName -> ContactName -> UTCTime -> IO ()
|
||||
updateContactLDN_ db userId contactId displayName newName updatedAt = do
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE contacts SET local_display_name = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?"
|
||||
@@ -509,7 +501,7 @@ updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt
|
||||
db
|
||||
"UPDATE group_members SET local_display_name = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?"
|
||||
(newName, updatedAt, userId, contactId)
|
||||
safeDeleteLDN db user displayName
|
||||
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (displayName, userId)
|
||||
|
||||
getContactByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Contact
|
||||
getContactByName db user localDisplayName = do
|
||||
@@ -622,7 +614,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (Vers
|
||||
WHERE user_id = ? AND contact_request_id = ?
|
||||
|]
|
||||
(invId, minV, maxV, ldn, currentTs, userId, cReqId)
|
||||
safeDeleteLDN db user oldLdn
|
||||
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (oldLdn, userId)
|
||||
where
|
||||
updateProfile currentTs =
|
||||
DB.execute
|
||||
@@ -692,9 +684,8 @@ deleteContactRequest db User {userId} contactRequestId = do
|
||||
SELECT local_display_name FROM contact_requests
|
||||
WHERE user_id = ? AND contact_request_id = ?
|
||||
)
|
||||
AND local_display_name NOT IN (SELECT local_display_name FROM users WHERE user_id = ?)
|
||||
|]
|
||||
(userId, userId, contactRequestId, userId)
|
||||
(userId, userId, contactRequestId)
|
||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
|
||||
|
||||
createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionRange -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> Bool -> IO Contact
|
||||
|
||||
@@ -14,6 +14,7 @@ module Simplex.Chat.Store.Files
|
||||
( getLiveSndFileTransfers,
|
||||
getLiveRcvFileTransfers,
|
||||
getPendingSndChunks,
|
||||
createSndDirectFileTransfer,
|
||||
createSndDirectFTConnection,
|
||||
createSndGroupFileTransfer,
|
||||
createSndGroupFileTransferConnection,
|
||||
@@ -168,6 +169,23 @@ getPendingSndChunks db fileId connId =
|
||||
|]
|
||||
(fileId, connId)
|
||||
|
||||
createSndDirectFileTransfer :: DB.Connection -> UserId -> Contact -> FilePath -> FileInvitation -> Maybe ConnId -> Integer -> SubscriptionMode -> IO FileTransferMeta
|
||||
createSndDirectFileTransfer db userId Contact {contactId} filePath FileInvitation {fileName, fileSize, fileInline} acId_ chunkSize subMode = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO files (user_id, contact_id, file_name, file_path, file_size, chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)"
|
||||
((userId, contactId, fileName, filePath, fileSize, chunkSize) :. (fileInline, CIFSSndStored, FPSMP, currentTs, currentTs))
|
||||
fileId <- insertedRowId db
|
||||
forM_ acId_ $ \acId -> do
|
||||
Connection {connId} <- createSndFileConnection_ db userId fileId acId subMode
|
||||
let fileStatus = FSNew
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO snd_files (file_id, file_status, file_inline, connection_id, created_at, updated_at) VALUES (?,?,?,?,?,?)"
|
||||
(fileId, fileStatus, fileInline, connId, currentTs, currentTs)
|
||||
pure FileTransferMeta {fileId, xftpSndFile = Nothing, fileName, filePath, fileSize, fileInline, chunkSize, cancelled = False}
|
||||
|
||||
createSndDirectFTConnection :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> SubscriptionMode -> IO ()
|
||||
createSndDirectFTConnection db user@User {userId} fileId (cmdId, acId) subMode = do
|
||||
currentTs <- getCurrentTime
|
||||
|
||||
@@ -225,9 +225,8 @@ deleteGroupLink db User {userId} GroupInfo {groupId} = do
|
||||
JOIN user_contact_links uc USING (user_contact_link_id)
|
||||
WHERE uc.user_id = ? AND uc.group_id = ?
|
||||
)
|
||||
AND local_display_name NOT IN (SELECT local_display_name FROM users WHERE user_id = ?)
|
||||
|]
|
||||
(userId, userId, groupId, userId)
|
||||
(userId, userId, groupId)
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
@@ -587,7 +586,7 @@ deleteGroup :: DB.Connection -> User -> GroupInfo -> IO ()
|
||||
deleteGroup db user@User {userId} g@GroupInfo {groupId, localDisplayName} = do
|
||||
deleteGroupProfile_ db userId groupId
|
||||
DB.execute db "DELETE FROM groups WHERE user_id = ? AND group_id = ?" (userId, groupId)
|
||||
safeDeleteLDN db user localDisplayName
|
||||
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||
forM_ (incognitoMembershipProfile g) $ deleteUnusedIncognitoProfileById_ db user . localProfileId
|
||||
|
||||
deleteGroupProfile_ :: DB.Connection -> UserId -> GroupId -> IO ()
|
||||
@@ -1045,14 +1044,14 @@ deleteGroupMember db user@User {userId} m@GroupMember {groupMemberId, groupId, m
|
||||
when (memberIncognito m) $ deleteUnusedIncognitoProfileById_ db user $ localProfileId memberProfile
|
||||
|
||||
cleanupMemberProfileAndName_ :: DB.Connection -> User -> GroupMember -> IO ()
|
||||
cleanupMemberProfileAndName_ db user@User {userId} GroupMember {groupMemberId, memberContactId, memberContactProfileId, localDisplayName} =
|
||||
cleanupMemberProfileAndName_ db User {userId} GroupMember {groupMemberId, memberContactId, memberContactProfileId, localDisplayName} =
|
||||
-- check record has no memberContactId (contact_id) - it means contact has been deleted and doesn't use profile & ldn
|
||||
when (isNothing memberContactId) $ do
|
||||
-- check other group member records don't use profile & ldn
|
||||
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)
|
||||
when (isNothing sameProfileMember) $ do
|
||||
DB.execute db "DELETE FROM contact_profiles WHERE user_id = ? AND contact_profile_id = ?" (userId, memberContactProfileId)
|
||||
safeDeleteLDN db user localDisplayName
|
||||
DB.execute db "DELETE FROM display_names WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||
|
||||
deleteGroupMemberConnection :: DB.Connection -> User -> GroupMember -> IO ()
|
||||
deleteGroupMemberConnection db User {userId} GroupMember {groupMemberId} =
|
||||
@@ -1331,7 +1330,7 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = do
|
||||
maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db user) contactId_
|
||||
|
||||
updateGroupProfile :: DB.Connection -> User -> GroupInfo -> GroupProfile -> ExceptT StoreError IO GroupInfo
|
||||
updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, description, image, groupPreferences}
|
||||
updateGroupProfile db User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, description, image, groupPreferences}
|
||||
| displayName == newName = liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
updateGroupProfile_ currentTs
|
||||
@@ -1362,7 +1361,7 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName,
|
||||
db
|
||||
"UPDATE groups SET local_display_name = ?, updated_at = ? WHERE user_id = ? AND group_id = ?"
|
||||
(ldn, currentTs, userId, groupId)
|
||||
safeDeleteLDN db user localDisplayName
|
||||
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (localDisplayName, userId)
|
||||
|
||||
getGroupInfo :: DB.Connection -> VersionRange -> User -> Int64 -> ExceptT StoreError IO GroupInfo
|
||||
getGroupInfo db vr User {userId, userContactId} groupId =
|
||||
@@ -1465,7 +1464,7 @@ getMatchingContacts db user@User {userId} Contact {contactId, profile = LocalPro
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id
|
||||
WHERE ct.user_id = ? AND ct.contact_id != ?
|
||||
AND ct.contact_status = ? AND ct.deleted = 0 AND ct.is_user = 0
|
||||
AND ct.contact_status = ? AND ct.deleted = 0
|
||||
AND p.display_name = ? AND p.full_name = ?
|
||||
|]
|
||||
|
||||
@@ -1503,7 +1502,7 @@ getMatchingMemberContacts db user@User {userId} GroupMember {memberProfile = Loc
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id
|
||||
WHERE ct.user_id = ?
|
||||
AND ct.contact_status = ? AND ct.deleted = 0 AND ct.is_user = 0
|
||||
AND ct.contact_status = ? AND ct.deleted = 0
|
||||
AND p.display_name = ? AND p.full_name = ?
|
||||
|]
|
||||
|
||||
@@ -1616,8 +1615,6 @@ mergeContactRecords db user@User {userId} to@Contact {localDisplayName = keepLDN
|
||||
let (toCt, fromCt) = toFromContacts to from
|
||||
Contact {contactId = toContactId, localDisplayName = toLDN} = toCt
|
||||
Contact {contactId = fromContactId, localDisplayName = fromLDN} = fromCt
|
||||
assertNotUser db user toCt
|
||||
assertNotUser db user fromCt
|
||||
liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
-- next query fixes incorrect unused contacts deletion
|
||||
@@ -2021,7 +2018,7 @@ createMemberContactConn_
|
||||
pure Connection {connId, agentConnId = AgentConnId acId, peerChatVRange, connType = ConnContact, contactConnInitiated = False, entityId = Just contactId, viaContact = Nothing, viaUserContactLink = Nothing, viaGroupLink = False, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnJoined, localAlias = "", createdAt = currentTs, connectionCode = Nothing, authErrCounter = 0}
|
||||
|
||||
updateMemberProfile :: DB.Connection -> User -> GroupMember -> Profile -> ExceptT StoreError IO GroupMember
|
||||
updateMemberProfile db user@User {userId} m p'
|
||||
updateMemberProfile db User {userId} m p'
|
||||
| displayName == newName = do
|
||||
liftIO $ updateMemberContactProfileReset_ db userId profileId p'
|
||||
pure m {memberProfile = profile}
|
||||
@@ -2033,7 +2030,7 @@ updateMemberProfile db user@User {userId} m p'
|
||||
db
|
||||
"UPDATE group_members SET local_display_name = ?, updated_at = ? WHERE user_id = ? AND group_member_id = ?"
|
||||
(ldn, currentTs, userId, groupMemberId)
|
||||
safeDeleteLDN db user localDisplayName
|
||||
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (localDisplayName, userId)
|
||||
pure $ Right m {localDisplayName = ldn, memberProfile = profile}
|
||||
where
|
||||
GroupMember {groupMemberId, localDisplayName, memberProfile = LocalProfile {profileId, displayName, localAlias}} = m
|
||||
@@ -2041,7 +2038,7 @@ updateMemberProfile db user@User {userId} m p'
|
||||
profile = toLocalProfile profileId p' localAlias
|
||||
|
||||
updateContactMemberProfile :: DB.Connection -> User -> GroupMember -> Contact -> Profile -> ExceptT StoreError IO (GroupMember, Contact)
|
||||
updateContactMemberProfile db user@User {userId} m ct@Contact {contactId} p'
|
||||
updateContactMemberProfile db User {userId} m ct@Contact {contactId} p'
|
||||
| displayName == newName = do
|
||||
liftIO $ updateMemberContactProfile_ db userId profileId p'
|
||||
pure (m {memberProfile = profile}, ct {profile} :: Contact)
|
||||
@@ -2049,7 +2046,7 @@ updateContactMemberProfile db user@User {userId} m ct@Contact {contactId} p'
|
||||
ExceptT . withLocalDisplayName db userId newName $ \ldn -> do
|
||||
currentTs <- getCurrentTime
|
||||
updateMemberContactProfile_' db userId profileId p' currentTs
|
||||
updateContactLDN_ db user contactId localDisplayName ldn currentTs
|
||||
updateContactLDN_ db userId contactId localDisplayName ldn currentTs
|
||||
pure $ Right (m {localDisplayName = ldn, memberProfile = profile}, ct {localDisplayName = ldn, profile} :: Contact)
|
||||
where
|
||||
GroupMember {localDisplayName, memberProfile = LocalProfile {profileId, displayName, localAlias}} = m
|
||||
|
||||
@@ -267,7 +267,7 @@ updateUserProfile db user p'
|
||||
"INSERT INTO display_names (local_display_name, ldn_base, user_id, created_at, updated_at) VALUES (?,?,?,?,?)"
|
||||
(newName, newName, userId, currentTs, currentTs)
|
||||
updateContactProfile_' db userId profileId p' currentTs
|
||||
updateContactLDN_ db user userContactId localDisplayName newName currentTs
|
||||
updateContactLDN_ db userId userContactId localDisplayName newName currentTs
|
||||
pure user {localDisplayName = newName, profile, fullPreferences, userMemberProfileUpdatedAt = userMemberProfileUpdatedAt'}
|
||||
where
|
||||
updateUserMemberProfileUpdatedAt_ currentTs
|
||||
@@ -388,7 +388,6 @@ deleteUserAddress db user@User {userId} = do
|
||||
JOIN user_contact_links uc USING (user_contact_link_id)
|
||||
WHERE uc.user_id = :user_id AND uc.local_display_name = '' AND uc.group_id IS NULL
|
||||
)
|
||||
AND local_display_name NOT IN (SELECT local_display_name FROM users WHERE user_id = :user_id)
|
||||
|]
|
||||
[":user_id" := userId]
|
||||
DB.executeNamed
|
||||
|
||||
@@ -110,7 +110,6 @@ data StoreError
|
||||
| SERemoteHostDuplicateCA
|
||||
| SERemoteCtrlNotFound {remoteCtrlId :: RemoteCtrlId}
|
||||
| SERemoteCtrlDuplicateCA
|
||||
| SEProhibitedDeleteUser {userId :: UserId, contactId :: ContactId}
|
||||
deriving (Show, Exception)
|
||||
|
||||
$(J.deriveJSON (sumTypeJSON $ dropPrefix "SE") ''StoreError)
|
||||
@@ -402,33 +401,3 @@ createWithRandomBytes' size gVar create = tryCreate 3
|
||||
|
||||
encodedRandomBytes :: TVar ChaChaDRG -> Int -> IO ByteString
|
||||
encodedRandomBytes gVar n = atomically $ B64.encode <$> C.randomBytes n gVar
|
||||
|
||||
assertNotUser :: DB.Connection -> User -> Contact -> ExceptT StoreError IO ()
|
||||
assertNotUser db User {userId} Contact {contactId, localDisplayName} = do
|
||||
r :: (Maybe Int64) <-
|
||||
-- This query checks that the foreign keys in the users table
|
||||
-- are not referencing the contact about to be deleted.
|
||||
-- With the current schema it would cause cascade delete of user,
|
||||
-- with mofified schema (in v5.6.0-beta.0) it would cause foreign key violation error.
|
||||
liftIO . maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT 1 FROM users
|
||||
WHERE (user_id = ? AND local_display_name = ?)
|
||||
OR contact_id = ?
|
||||
LIMIT 1
|
||||
|]
|
||||
(userId, localDisplayName, contactId)
|
||||
when (isJust r) $ throwError $ SEProhibitedDeleteUser userId contactId
|
||||
|
||||
safeDeleteLDN :: DB.Connection -> User -> ContactName -> IO ()
|
||||
safeDeleteLDN db User {userId} localDisplayName = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
DELETE FROM display_names
|
||||
WHERE user_id = ? AND local_display_name = ?
|
||||
AND local_display_name NOT IN (SELECT local_display_name FROM users WHERE user_id = ?)
|
||||
|]
|
||||
(userId, localDisplayName, userId)
|
||||
|
||||
@@ -15,7 +15,6 @@ import Control.Concurrent.STM
|
||||
import Control.Exception (bracket, bracket_)
|
||||
import Control.Monad
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.Reader
|
||||
import Data.ByteArray (ScrubbedBytes)
|
||||
import Data.Functor (($>))
|
||||
import Data.List (dropWhileEnd, find)
|
||||
@@ -23,7 +22,7 @@ import Data.Maybe (isNothing)
|
||||
import qualified Data.Text as T
|
||||
import Network.Socket
|
||||
import Simplex.Chat
|
||||
import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..))
|
||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..))
|
||||
import Simplex.Chat.Core
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Store
|
||||
@@ -130,7 +129,8 @@ testCfg =
|
||||
{ agentConfig = testAgentCfg,
|
||||
showReceipts = False,
|
||||
testView = True,
|
||||
tbqSize = 16
|
||||
tbqSize = 16,
|
||||
xftpFileConfig = Nothing
|
||||
}
|
||||
|
||||
testAgentCfgVPrev :: AgentConfig
|
||||
@@ -209,7 +209,6 @@ startTestChat_ db cfg opts user = do
|
||||
t <- withVirtualTerminal termSettings pure
|
||||
ct <- newChatTerminal t opts
|
||||
cc <- newChatController db (Just user) cfg opts False
|
||||
void $ execChatCommand' (SetTempFolder "tests/tmp/tmp") `runReaderT` cc
|
||||
chatAsync <- async . runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts
|
||||
atomically . unless (maintenance opts) $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry
|
||||
termQ <- newTQueueIO
|
||||
|
||||
@@ -1067,7 +1067,7 @@ testChatWorking alice bob = do
|
||||
alice <# "bob> hello too"
|
||||
|
||||
testMaintenanceModeWithFiles :: HasCallStack => FilePath -> IO ()
|
||||
testMaintenanceModeWithFiles tmp = withXFTPServer $ do
|
||||
testMaintenanceModeWithFiles tmp = do
|
||||
withNewTestChat tmp "bob" bobProfile $ \bob -> do
|
||||
withNewTestChatOpts tmp testOpts {maintenance = True} "alice" aliceProfile $ \alice -> do
|
||||
alice ##> "/_start"
|
||||
@@ -1075,26 +1075,12 @@ testMaintenanceModeWithFiles tmp = withXFTPServer $ do
|
||||
alice ##> "/_files_folder ./tests/tmp/alice_files"
|
||||
alice <## "ok"
|
||||
connectUsers alice bob
|
||||
|
||||
bob #> "/f @alice ./tests/fixtures/test.jpg"
|
||||
bob <## "use /fc 1 to cancel sending"
|
||||
alice <# "bob> sends file test.jpg (136.5 KiB / 139737 bytes)"
|
||||
alice <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
bob <## "completed uploading file 1 (test.jpg) for alice"
|
||||
|
||||
alice ##> "/fr 1"
|
||||
alice
|
||||
<### [ "saving file 1 from bob to test.jpg",
|
||||
"started receiving file 1 (test.jpg) from bob"
|
||||
]
|
||||
startFileTransferWithDest' bob alice "test.jpg" "136.5 KiB / 139737 bytes" Nothing
|
||||
bob <## "completed sending file 1 (test.jpg) to alice"
|
||||
alice <## "completed receiving file 1 (test.jpg) from bob"
|
||||
|
||||
src <- B.readFile "./tests/fixtures/test.jpg"
|
||||
dest <- B.readFile "./tests/tmp/alice_files/test.jpg"
|
||||
dest `shouldBe` src
|
||||
|
||||
B.readFile "./tests/tmp/alice_files/test.jpg" `shouldReturn` src
|
||||
threadDelay 500000
|
||||
|
||||
alice ##> "/_stop"
|
||||
alice <## "chat stopped"
|
||||
alice ##> "/_db export {\"archivePath\": \"./tests/tmp/alice-chat.zip\"}"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ import Control.Monad (void, when)
|
||||
import qualified Data.ByteString as B
|
||||
import Data.List (isInfixOf)
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.Controller (ChatConfig (..))
|
||||
import Simplex.Chat.Controller (ChatConfig (..), XFTPFileConfig (..))
|
||||
import Simplex.Chat.Protocol (supportedChatVRange)
|
||||
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
|
||||
import Simplex.Chat.Types (GroupMemberRole (..))
|
||||
@@ -4321,7 +4321,7 @@ testGroupMsgForwardDeletion =
|
||||
|
||||
testGroupMsgForwardFile :: HasCallStack => FilePath -> IO ()
|
||||
testGroupMsgForwardFile =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> withXFTPServer $ do
|
||||
setupGroupForwarding3 "team" alice bob cath
|
||||
|
||||
@@ -4343,6 +4343,8 @@ testGroupMsgForwardFile =
|
||||
src <- B.readFile "./tests/fixtures/test.jpg"
|
||||
dest <- B.readFile "./tests/tmp/test.jpg"
|
||||
dest `shouldBe` src
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
|
||||
|
||||
testGroupMsgForwardChangeRole :: HasCallStack => FilePath -> IO ()
|
||||
testGroupMsgForwardChangeRole =
|
||||
@@ -4575,7 +4577,7 @@ testGroupHistoryPreferenceOff =
|
||||
|
||||
testGroupHistoryHostFile :: HasCallStack => FilePath -> IO ()
|
||||
testGroupHistoryHostFile =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> withXFTPServer $ do
|
||||
createGroup2 "team" alice bob
|
||||
|
||||
@@ -4611,10 +4613,12 @@ testGroupHistoryHostFile =
|
||||
src <- B.readFile "./tests/fixtures/test.jpg"
|
||||
dest <- B.readFile "./tests/tmp/test.jpg"
|
||||
dest `shouldBe` src
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
|
||||
|
||||
testGroupHistoryMemberFile :: HasCallStack => FilePath -> IO ()
|
||||
testGroupHistoryMemberFile =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> withXFTPServer $ do
|
||||
createGroup2 "team" alice bob
|
||||
|
||||
@@ -4650,6 +4654,8 @@ testGroupHistoryMemberFile =
|
||||
src <- B.readFile "./tests/fixtures/test.jpg"
|
||||
dest <- B.readFile "./tests/tmp/test.jpg"
|
||||
dest `shouldBe` src
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
|
||||
|
||||
testGroupHistoryLargeFile :: HasCallStack => FilePath -> IO ()
|
||||
testGroupHistoryLargeFile =
|
||||
@@ -4707,11 +4713,11 @@ testGroupHistoryLargeFile =
|
||||
destCath <- B.readFile "./tests/tmp/testfile_2"
|
||||
destCath `shouldBe` src
|
||||
where
|
||||
cfg = testCfg {xftpDescrPartSize = 200}
|
||||
cfg = testCfg {xftpDescrPartSize = 200, xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
|
||||
|
||||
testGroupHistoryMultipleFiles :: HasCallStack => FilePath -> IO ()
|
||||
testGroupHistoryMultipleFiles =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> withXFTPServer $ do
|
||||
xftpCLI ["rand", "./tests/tmp/testfile_bob", "2mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile_bob"]
|
||||
xftpCLI ["rand", "./tests/tmp/testfile_alice", "1mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile_alice"]
|
||||
@@ -4788,10 +4794,12 @@ testGroupHistoryMultipleFiles =
|
||||
`shouldContain` [ ((0, "hi alice"), Just "./tests/tmp/testfile_bob_1"),
|
||||
((0, "hey bob"), Just "./tests/tmp/testfile_alice_1")
|
||||
]
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
|
||||
|
||||
testGroupHistoryFileCancel :: HasCallStack => FilePath -> IO ()
|
||||
testGroupHistoryFileCancel =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> withXFTPServer $ do
|
||||
xftpCLI ["rand", "./tests/tmp/testfile_bob", "2mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile_bob"]
|
||||
xftpCLI ["rand", "./tests/tmp/testfile_alice", "1mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile_alice"]
|
||||
@@ -4843,10 +4851,12 @@ testGroupHistoryFileCancel =
|
||||
bob <## "#team: alice added cath (Catherine) to the group (connecting...)"
|
||||
bob <## "#team: new member cath is connected"
|
||||
]
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
|
||||
|
||||
testGroupHistoryFileCancelNoText :: HasCallStack => FilePath -> IO ()
|
||||
testGroupHistoryFileCancelNoText =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> withXFTPServer $ do
|
||||
xftpCLI ["rand", "./tests/tmp/testfile_bob", "2mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile_bob"]
|
||||
xftpCLI ["rand", "./tests/tmp/testfile_alice", "1mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile_alice"]
|
||||
@@ -4902,6 +4912,8 @@ testGroupHistoryFileCancelNoText =
|
||||
bob <## "#team: alice added cath (Catherine) to the group (connecting...)"
|
||||
bob <## "#team: new member cath is connected"
|
||||
]
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
|
||||
|
||||
testGroupHistoryQuotes :: HasCallStack => FilePath -> IO ()
|
||||
testGroupHistoryQuotes =
|
||||
|
||||
@@ -12,6 +12,7 @@ import Simplex.Chat.Controller (ChatConfig (..), InlineFilesConfig (..), default
|
||||
import System.Directory (copyFile, doesFileExist)
|
||||
import System.FilePath ((</>))
|
||||
import Test.Hspec hiding (it)
|
||||
import UnliftIO.Async (concurrently_)
|
||||
|
||||
chatLocalChatsTests :: SpecWith FilePath
|
||||
chatLocalChatsTests = do
|
||||
@@ -157,24 +158,24 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do
|
||||
|
||||
testOtherFiles :: FilePath -> IO ()
|
||||
testOtherFiles =
|
||||
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do
|
||||
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
|
||||
connectUsers alice bob
|
||||
createCCNoteFolder bob
|
||||
bob ##> "/_files_folder ./tests/tmp/"
|
||||
bob <## "ok"
|
||||
|
||||
alice #> "/f @bob ./tests/fixtures/test.jpg"
|
||||
alice <## "use /fc 1 to cancel sending"
|
||||
alice ##> "/_send @2 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/test.jpg\"}"
|
||||
alice <# "@bob voice message (00:10)"
|
||||
alice <# "/f @bob ./tests/fixtures/test.jpg"
|
||||
-- below is not shown in "sent" mode
|
||||
-- alice <## "use /fc 1 to cancel sending"
|
||||
bob <# "alice> voice message (00:10)"
|
||||
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
|
||||
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
alice <## "completed uploading file 1 (test.jpg) for bob"
|
||||
|
||||
bob ##> "/fr 1"
|
||||
bob
|
||||
<### [ "saving file 1 from alice to test.jpg",
|
||||
"started receiving file 1 (test.jpg) from alice"
|
||||
]
|
||||
bob <## "completed receiving file 1 (test.jpg) from alice"
|
||||
-- below is not shown in "sent" mode
|
||||
-- bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
bob <## "started receiving file 1 (test.jpg) from alice"
|
||||
concurrently_
|
||||
(alice <## "completed sending file 1 (test.jpg) to bob")
|
||||
(bob <## "completed receiving file 1 (test.jpg) from alice")
|
||||
|
||||
bob /* "test"
|
||||
bob ##> "/tail *"
|
||||
|
||||
@@ -1493,7 +1493,7 @@ testSetConnectionAlias = testChat2 aliceProfile bobProfile $
|
||||
|
||||
testSetContactPrefs :: HasCallStack => FilePath -> IO ()
|
||||
testSetContactPrefs = testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> withXFTPServer $ do
|
||||
\alice bob -> do
|
||||
alice #$> ("/_files_folder ./tests/tmp/alice", id, "ok")
|
||||
bob #$> ("/_files_folder ./tests/tmp/bob", id, "ok")
|
||||
createDirectoryIfMissing True "./tests/tmp/alice"
|
||||
@@ -1528,24 +1528,15 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $
|
||||
bob #$> ("/_get chat @2 count=100", chat, startFeatures <> [(0, "Voice messages: enabled for you")])
|
||||
alice ##> sendVoice
|
||||
alice <## voiceNotAllowed
|
||||
|
||||
-- sending voice message allowed
|
||||
bob ##> sendVoice
|
||||
bob <# "@alice voice message (00:10)"
|
||||
bob <# "/f @alice test.txt"
|
||||
bob <## "use /fc 1 to cancel sending"
|
||||
bob <## "completed sending file 1 (test.txt) to alice"
|
||||
alice <# "bob> voice message (00:10)"
|
||||
alice <# "bob> sends file test.txt (11 bytes / 11 bytes)"
|
||||
alice <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
bob <## "completed uploading file 1 (test.txt) for alice"
|
||||
alice ##> "/fr 1"
|
||||
alice
|
||||
<### [ "saving file 1 from bob to test_1.txt",
|
||||
"started receiving file 1 (test.txt) from bob"
|
||||
]
|
||||
alice <## "started receiving file 1 (test.txt) from bob"
|
||||
alice <## "completed receiving file 1 (test.txt) from bob"
|
||||
(bob </)
|
||||
|
||||
-- alice ##> "/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"Alice\", \"preferences\": {\"voice\": {\"allow\": \"no\"}}}"
|
||||
alice ##> "/set voice no"
|
||||
alice <## "updated preferences:"
|
||||
|
||||
@@ -19,7 +19,7 @@ import Data.Maybe (fromMaybe)
|
||||
import Data.String
|
||||
import qualified Data.Text as T
|
||||
import Database.SQLite.Simple (Only (..))
|
||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..))
|
||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig)
|
||||
import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Store.NoteFolders (createNoteFolder)
|
||||
import Simplex.Chat.Store.Profiles (getUserContactProfiles)
|
||||
@@ -32,6 +32,7 @@ import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Version
|
||||
import System.Directory (doesFileExist)
|
||||
import System.Environment (lookupEnv, withArgs)
|
||||
import System.FilePath ((</>))
|
||||
import System.IO.Silently (capture_)
|
||||
import System.Info (os)
|
||||
import Test.Hspec hiding (it)
|
||||
@@ -95,6 +96,29 @@ versionTestMatrix3 runTest = do
|
||||
it "curr to prev" $ runTestCfg3 testCfgVPrev testCfg testCfg runTest
|
||||
it "curr+prev to prev" $ runTestCfg3 testCfgVPrev testCfg testCfgVPrev runTest
|
||||
|
||||
inlineCfg :: Integer -> ChatConfig
|
||||
inlineCfg n = testCfg {inlineFiles = defaultInlineFilesConfig {sendChunks = 0, offerChunks = n, receiveChunks = n}}
|
||||
|
||||
fileTestMatrix2 :: (HasCallStack => TestCC -> TestCC -> IO ()) -> SpecWith FilePath
|
||||
fileTestMatrix2 runTest = do
|
||||
it "via connection" $ runTestCfg2 viaConn viaConn runTest
|
||||
it "inline (accepting)" $ runTestCfg2 inline inline runTest
|
||||
it "via connection (inline offered)" $ runTestCfg2 inline viaConn runTest
|
||||
it "via connection (inline supported)" $ runTestCfg2 viaConn inline runTest
|
||||
where
|
||||
inline = inlineCfg 100
|
||||
viaConn = inlineCfg 0
|
||||
|
||||
fileTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith FilePath
|
||||
fileTestMatrix3 runTest = do
|
||||
it "via connection" $ runTestCfg3 viaConn viaConn viaConn runTest
|
||||
it "inline" $ runTestCfg3 inline inline inline runTest
|
||||
it "via connection (inline offered)" $ runTestCfg3 inline viaConn viaConn runTest
|
||||
it "via connection (inline supported)" $ runTestCfg3 viaConn inline inline runTest
|
||||
where
|
||||
inline = inlineCfg 100
|
||||
viaConn = inlineCfg 0
|
||||
|
||||
runTestCfg2 :: ChatConfig -> ChatConfig -> (HasCallStack => TestCC -> TestCC -> IO ()) -> FilePath -> IO ()
|
||||
runTestCfg2 aliceCfg bobCfg runTest tmp =
|
||||
withNewTestChatCfg tmp aliceCfg "alice" aliceProfile $ \alice ->
|
||||
@@ -571,6 +595,20 @@ checkActionDeletesFile file action = do
|
||||
fileExistsAfter <- doesFileExist file
|
||||
fileExistsAfter `shouldBe` False
|
||||
|
||||
startFileTransferWithDest' :: HasCallStack => TestCC -> TestCC -> String -> String -> Maybe String -> IO ()
|
||||
startFileTransferWithDest' cc1 cc2 fileName fileSize fileDest_ = do
|
||||
name1 <- userName cc1
|
||||
name2 <- userName cc2
|
||||
cc1 #> ("/f @" <> name2 <> " ./tests/fixtures/" <> fileName)
|
||||
cc1 <## "use /fc 1 to cancel sending"
|
||||
cc2 <# (name1 <> "> sends file " <> fileName <> " (" <> fileSize <> ")")
|
||||
cc2 <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
cc2 ##> ("/fr 1" <> maybe "" (" " <>) fileDest_)
|
||||
cc2 <## ("saving file 1 from " <> name1 <> " to " <> maybe id (</>) fileDest_ fileName)
|
||||
concurrently_
|
||||
(cc2 <## ("started receiving file 1 (" <> fileName <> ") from " <> name1))
|
||||
(cc1 <## ("started sending file 1 (" <> fileName <> ") to " <> name2))
|
||||
|
||||
currentChatVRangeInfo :: String
|
||||
currentChatVRangeInfo =
|
||||
"peer chat protocol version range: " <> vRangeStr supportedChatVRange
|
||||
|
||||
@@ -13,7 +13,7 @@ import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import qualified Data.Map.Strict as M
|
||||
import Simplex.Chat.Archive (archiveFilesFolder)
|
||||
import Simplex.Chat.Controller (versionNumber)
|
||||
import Simplex.Chat.Controller (ChatConfig (..), XFTPFileConfig (..), versionNumber)
|
||||
import qualified Simplex.Chat.Controller as Controller
|
||||
import Simplex.Chat.Mobile.File
|
||||
import Simplex.Chat.Remote.Types
|
||||
@@ -194,7 +194,7 @@ remoteMessageTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob
|
||||
|
||||
remoteStoreFileTest :: HasCallStack => FilePath -> IO ()
|
||||
remoteStoreFileTest =
|
||||
testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob ->
|
||||
testChatCfg3 cfg aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob ->
|
||||
withXFTPServer $ do
|
||||
let mobileFiles = "./tests/tmp/mobile_files"
|
||||
mobile ##> ("/_files_folder " <> mobileFiles)
|
||||
@@ -317,13 +317,15 @@ remoteStoreFileTest =
|
||||
|
||||
stopMobile mobile desktop
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp/tmp"}
|
||||
hostError cc err = do
|
||||
r <- getTermLine cc
|
||||
r `shouldStartWith` "remote host 1 error"
|
||||
r `shouldContain` err
|
||||
|
||||
remoteCLIFileTest :: HasCallStack => FilePath -> IO ()
|
||||
remoteCLIFileTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> withXFTPServer $ do
|
||||
remoteCLIFileTest = testChatCfg3 cfg aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> withXFTPServer $ do
|
||||
createDirectoryIfMissing True "./tests/tmp/tmp/"
|
||||
let mobileFiles = "./tests/tmp/mobile_files"
|
||||
mobile ##> ("/_files_folder " <> mobileFiles)
|
||||
mobile <## "ok"
|
||||
@@ -390,6 +392,8 @@ remoteCLIFileTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob
|
||||
B.readFile (bobFiles </> "test.jpg") `shouldReturn` src'
|
||||
|
||||
stopMobile mobile desktop
|
||||
where
|
||||
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp/tmp"}
|
||||
|
||||
switchRemoteHostTest :: FilePath -> IO ()
|
||||
switchRemoteHostTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> do
|
||||
|
||||
Reference in New Issue
Block a user