Compare commits

..

13 Commits

Author SHA1 Message Date
JRoberts
edf217111f 4.0.0 2022-09-20 19:11:52 +04:00
Evgeny Poberezkin
f0e18c62fe code: update simplexmq (async secure) 2022-09-20 15:42:36 +01:00
Evgeny Poberezkin
a615dbec91 support direct file invitations without contact requests (#1076) 2022-09-20 14:46:30 +01:00
JRoberts
a8ef92a933 android: version 4.0-beta.1 (54) 2022-09-20 14:38:05 +04:00
JRoberts
c1cca9385a ios: version 4.0 (73) 2022-09-20 14:13:04 +04:00
Stanislav Dmitrenko
267207cc15 android: Ability to delete app files and media (#1072)
* Ability to delete app files and media

* section title, corrections

* remove icon

* change translation

* revert disabled unless stopped

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-20 12:36:11 +04:00
JRoberts
012115b330 ios: disable files deletion unless chat is stopped (#1074) 2022-09-20 12:35:25 +04:00
JRoberts
c4aa988fb3 ios: version 4.0 (72) 2022-09-19 19:49:39 +04:00
JRoberts
c236a759d5 ios: clear storage translations (#1071) 2022-09-19 19:35:59 +04:00
JRoberts
67323a41eb ios: clear storage (#1069)
* wip

* display current storage state

* alert

* fix

* simplify

* remove unused function

* fix log

* replace prints with logger

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* low res will remain text

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-19 19:05:29 +04:00
JRoberts
962166c2ef mobile: prohibit /sql commands if unauthorized (#1068)
* ios: prohibit /sql commands if unauthorized

* refactor

* move check to send command

* revert diff

* refactor

* android

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* fix

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-19 13:02:48 +04:00
Evgeny Poberezkin
b0ed64533f update simplexmq 2022-09-18 13:54:33 +01:00
Evgeny Poberezkin
bc7fe4ec75 ios: update version to v4.0 2022-09-18 10:03:15 +01:00
23 changed files with 391 additions and 73 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId "chat.simplex.app" applicationId "chat.simplex.app"
minSdk 29 minSdk 29
targetSdk 32 targetSdk 32
versionCode 53 versionCode 54
versionName "4.0-beta.0" versionName "4.0-beta.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk { ndk {

View File

@@ -1833,13 +1833,15 @@ sealed class ChatError {
@Serializable @Serializable
sealed class ChatErrorType { sealed class ChatErrorType {
val string: String get() = when (this) { val string: String get() = when (this) {
is NoActiveUser -> "noActiveUser"
is InvalidConnReq -> "invalidConnReq" is InvalidConnReq -> "invalidConnReq"
is ContactGroups -> "groupNames $groupNames" is ContactGroups -> "groupNames $groupNames"
is NoActiveUser -> "noActiveUser" is СommandError -> "commandError $message"
} }
@Serializable @SerialName("noActiveUser") class NoActiveUser: ChatErrorType() @Serializable @SerialName("noActiveUser") class NoActiveUser: ChatErrorType()
@Serializable @SerialName("invalidConnReq") class InvalidConnReq: ChatErrorType() @Serializable @SerialName("invalidConnReq") class InvalidConnReq: ChatErrorType()
@Serializable @SerialName("contactGroups") class ContactGroups(val contact: Contact, val groupNames: List<String>): ChatErrorType() @Serializable @SerialName("contactGroups") class ContactGroups(val contact: Contact, val groupNames: List<String>): ChatErrorType()
@Serializable @SerialName("commandError") class СommandError(val message: String): ChatErrorType()
} }
@Serializable @Serializable

View File

@@ -46,14 +46,7 @@ fun TerminalView(chatModel: ChatModel, close: () -> Unit) {
TerminalLayout( TerminalLayout(
chatModel.terminalItems, chatModel.terminalItems,
composeState, composeState,
sendCommand = { sendCommand = { sendCommand(chatModel, composeState) },
withApi {
// show "in progress"
chatModel.controller.sendCmd(CC.Console(composeState.value.message))
composeState.value = ComposeState(useLinkPreviews = false)
// hide "in progress"
}
},
close close
) )
} else { } else {
@@ -91,6 +84,25 @@ private fun runAuth(authorized: MutableState<Boolean>, context: Context) {
) )
} }
private fun sendCommand(chatModel: ChatModel, composeState: MutableState<ComposeState>) {
val developerTools = chatModel.controller.appPrefs.developerTools.get()
val prefPerformLA = chatModel.controller.appPrefs.performLA.get()
val s = composeState.value.message
if (s.startsWith("/sql") && (!prefPerformLA || !developerTools)) {
val resp = CR.ChatCmdError(ChatError.ChatErrorChat(ChatErrorType.СommandError("Failed reading: empty")))
chatModel.terminalItems.add(TerminalItem.cmd(CC.Console(s)))
chatModel.terminalItems.add(TerminalItem.resp(resp))
composeState.value = ComposeState(useLinkPreviews = false)
} else {
withApi {
// show "in progress"
chatModel.controller.sendCmd(CC.Console(s))
composeState.value = ComposeState(useLinkPreviews = false)
// hide "in progress"
}
}
}
@Composable @Composable
fun TerminalLayout( fun TerminalLayout(
terminalItems: List<TerminalItem>, terminalItems: List<TerminalItem>,

View File

@@ -62,6 +62,7 @@ fun DatabaseView(
importArchiveAlert(m, context, uri, progressIndicator) importArchiveAlert(m, context, uri, progressIndicator)
} }
} }
val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(getAppFilesDirectory(context))) }
LaunchedEffect(m.chatRunning) { LaunchedEffect(m.chatRunning) {
runChat.value = m.chatRunning.value ?: true runChat.value = m.chatRunning.value ?: true
} }
@@ -78,10 +79,12 @@ fun DatabaseView(
chatArchiveName, chatArchiveName,
chatArchiveTime, chatArchiveTime,
chatLastStart, chatLastStart,
appFilesCountAndSize,
startChat = { startChat(m, runChat, chatLastStart, m.chatDbChanged) }, startChat = { startChat(m, runChat, chatLastStart, m.chatDbChanged) },
stopChatAlert = { stopChatAlert(m, runChat, context) }, stopChatAlert = { stopChatAlert(m, runChat, context) },
exportArchive = { exportArchive(context, m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) }, exportArchive = { exportArchive(context, m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) },
deleteChatAlert = { deleteChatAlert(m, progressIndicator) }, deleteChatAlert = { deleteChatAlert(m, progressIndicator) },
deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(context, appFilesCountAndSize) },
showSettingsModal showSettingsModal
) )
if (progressIndicator.value) { if (progressIndicator.value) {
@@ -112,10 +115,12 @@ fun DatabaseLayout(
chatArchiveName: MutableState<String?>, chatArchiveName: MutableState<String?>,
chatArchiveTime: MutableState<Instant?>, chatArchiveTime: MutableState<Instant?>,
chatLastStart: MutableState<Instant?>, chatLastStart: MutableState<Instant?>,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
startChat: () -> Unit, startChat: () -> Unit,
stopChatAlert: () -> Unit, stopChatAlert: () -> Unit,
exportArchive: () -> Unit, exportArchive: () -> Unit,
deleteChatAlert: () -> Unit, deleteChatAlert: () -> Unit,
deleteAppFilesAndMedia: () -> Unit,
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit) showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
) { ) {
val stopped = !runChat val stopped = !runChat
@@ -196,6 +201,28 @@ fun DatabaseLayout(
stringResource(R.string.stop_chat_to_enable_database_actions) stringResource(R.string.stop_chat_to_enable_database_actions)
} }
) )
SectionSpacer()
SectionView(stringResource(R.string.files_section)) {
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
SectionItemView(
deleteAppFilesAndMedia,
disabled = deleteFilesDisabled
) {
Text(
stringResource(R.string.delete_files_and_media),
color = if (deleteFilesDisabled) HighOrLowlight else Color.Red
)
}
}
val (count, size) = appFilesCountAndSize.value
SectionTextFooter(
if (count == 0) {
stringResource(R.string.no_received_app_files)
} else {
String.format(stringResource(R.string.total_files_count_and_size), count, formatBytes(size))
}
)
} }
} }
@@ -502,6 +529,21 @@ private fun deleteChat(m: ChatModel, progressIndicator: MutableState<Boolean>) {
} }
} }
private fun deleteFilesAndMediaAlert(context: Context, appFilesCountAndSize: MutableState<Pair<Int, Long>>) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_files_and_media_question),
text = generalGetString(R.string.delete_files_and_media_desc),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = { deleteFiles(appFilesCountAndSize, context) },
destructive = true
)
}
private fun deleteFiles(appFilesCountAndSize: MutableState<Pair<Int, Long>>, context: Context) {
deleteAppFiles(context)
appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory(context))
}
private fun operationEnded(m: ChatModel, progressIndicator: MutableState<Boolean>, alert: () -> Unit) { private fun operationEnded(m: ChatModel, progressIndicator: MutableState<Boolean>, alert: () -> Unit) {
m.chatDbChanged.value = true m.chatDbChanged.value = true
progressIndicator.value = false progressIndicator.value = false
@@ -527,10 +569,12 @@ fun PreviewDatabaseLayout() {
chatArchiveName = remember { mutableStateOf("dummy_archive") }, chatArchiveName = remember { mutableStateOf("dummy_archive") },
chatArchiveTime = remember { mutableStateOf(Clock.System.now()) }, chatArchiveTime = remember { mutableStateOf(Clock.System.now()) },
chatLastStart = remember { mutableStateOf(Clock.System.now()) }, chatLastStart = remember { mutableStateOf(Clock.System.now()) },
appFilesCountAndSize = remember { mutableStateOf(0 to 0L) },
startChat = {}, startChat = {},
stopChatAlert = {}, stopChatAlert = {},
exportArchive = {}, exportArchive = {},
deleteChatAlert = {}, deleteChatAlert = {},
deleteAppFilesAndMedia = {},
showSettingsModal = { {} } showSettingsModal = { {} }
) )
} }

View File

@@ -24,8 +24,7 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import chat.simplex.app.BuildConfig import chat.simplex.app.*
import chat.simplex.app.SimplexApp
import chat.simplex.app.model.CIFile import chat.simplex.app.model.CIFile
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.* import java.io.*
@@ -405,6 +404,31 @@ fun removeFile(context: Context, fileName: String): Boolean {
return fileDeleted return fileDeleted
} }
fun deleteAppFiles(context: Context) {
val dir = File(getAppFilesDirectory(context))
try {
dir.list()?.forEach {
removeFile(context, it)
}
} catch (e: java.lang.Exception) {
Log.e(TAG, "Util deleteAppFiles error: ${e.stackTraceToString()}")
}
}
fun directoryFileCountAndSize(dir: String): Pair<Int, Long> { // count, size in bytes
var fileCount = 0
var bytes = 0L
try {
File(dir).listFiles()?.forEach {
fileCount++
bytes += it.length()
}
} catch (e: java.lang.Exception) {
Log.e(TAG, "Util directoryFileCountAndSize error: ${e.stackTraceToString()}")
}
return fileCount to bytes
}
fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT) fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT)
fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT) fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT)

View File

@@ -556,6 +556,12 @@
<string name="restart_the_app_to_create_a_new_chat_profile">Перезапустите приложение, чтобы создать новый профиль.</string> <string name="restart_the_app_to_create_a_new_chat_profile">Перезапустите приложение, чтобы создать новый профиль.</string>
<string name="you_must_use_the_most_recent_version_of_database">Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе вы можете перестать получать сообщения от некоторых контактов.</string> <string name="you_must_use_the_most_recent_version_of_database">Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе вы можете перестать получать сообщения от некоторых контактов.</string>
<string name="stop_chat_to_enable_database_actions">Остановите чат, чтобы разблокировать операции с архивом чата.</string> <string name="stop_chat_to_enable_database_actions">Остановите чат, чтобы разблокировать операции с архивом чата.</string>
<string name="files_section">ФАЙЛЫ</string>
<string name="delete_files_and_media">Удалить файлы и медиа</string>
<string name="delete_files_and_media_question">Удалить файлы и медиа?</string>
<string name="delete_files_and_media_desc">Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении.</string>
<string name="no_received_app_files">Нет полученных или отправленных файлов</string>
<string name="total_files_count_and_size">%d файл(ов) общим размером %s</string>
<!-- DatabaseEncryptionView.kt --> <!-- DatabaseEncryptionView.kt -->
<string name="save_passphrase_in_keychain">Сохранить пароль в Keystore</string> <string name="save_passphrase_in_keychain">Сохранить пароль в Keystore</string>

View File

@@ -557,6 +557,12 @@
<string name="restart_the_app_to_create_a_new_chat_profile">Restart the app to create a new chat profile.</string> <string name="restart_the_app_to_create_a_new_chat_profile">Restart the app to create a new chat profile.</string>
<string name="you_must_use_the_most_recent_version_of_database">You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts.</string> <string name="you_must_use_the_most_recent_version_of_database">You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts.</string>
<string name="stop_chat_to_enable_database_actions">Stop chat to enable database actions.</string> <string name="stop_chat_to_enable_database_actions">Stop chat to enable database actions.</string>
<string name="files_section">FILES</string>
<string name="delete_files_and_media">Delete files \&amp; media</string>
<string name="delete_files_and_media_question">Delete files and media?</string>
<string name="delete_files_and_media_desc">This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</string>
<string name="no_received_app_files">No received or sent files</string>
<string name="total_files_count_and_size">%d file(s) with total size of %s</string>
<!-- DatabaseEncryptionView.kt --> <!-- DatabaseEncryptionView.kt -->
<string name="save_passphrase_in_keychain">Save passphrase in Keystore</string> <string name="save_passphrase_in_keychain">Save passphrase in Keystore</string>

View File

@@ -17,6 +17,7 @@ enum DatabaseAlert: Identifiable {
case deleteChat case deleteChat
case chatDeleted case chatDeleted
case deleteLegacyDatabase case deleteLegacyDatabase
case deleteFilesAndMedia
case error(title: LocalizedStringKey, error: String = "") case error(title: LocalizedStringKey, error: String = "")
var id: String { var id: String {
@@ -28,6 +29,7 @@ enum DatabaseAlert: Identifiable {
case .deleteChat: return "deleteChat" case .deleteChat: return "deleteChat"
case .chatDeleted: return "chatDeleted" case .chatDeleted: return "chatDeleted"
case .deleteLegacyDatabase: return "deleteLegacyDatabase" case .deleteLegacyDatabase: return "deleteLegacyDatabase"
case .deleteFilesAndMedia: return "deleteFilesAndMedia"
case let .error(title, _): return "error \(title)" case let .error(title, _): return "error \(title)"
} }
} }
@@ -46,6 +48,7 @@ struct DatabaseView: View {
@State private var dbContainer = dbContainerGroupDefault.get() @State private var dbContainer = dbContainerGroupDefault.get()
@State private var legacyDatabase = hasLegacyDatabase() @State private var legacyDatabase = hasLegacyDatabase()
@State private var useKeychain = storeDBPassphraseGroupDefault.get() @State private var useKeychain = storeDBPassphraseGroupDefault.get()
@State private var appFilesCountAndSize: (Int, Int)?
var body: some View { var body: some View {
ZStack { ZStack {
@@ -147,8 +150,28 @@ struct DatabaseView: View {
} }
} }
} }
Section {
Button("Delete files & media", role: .destructive) {
alert = .deleteFilesAndMedia
}
} header: {
Text("Files")
} footer: {
if let (fileCount, size) = appFilesCountAndSize {
if fileCount == 0 {
Text("No received or sent files")
} else {
Text("\(fileCount) file(s) with total size of \(ByteCountFormatter().string(fromByteCount: Int64(size)))")
}
}
}
.disabled(!stopped || appFilesCountAndSize?.0 == 0)
}
.onAppear {
runChat = m.chatRunning ?? true
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
} }
.onAppear { runChat = m.chatRunning ?? true }
.alert(item: $alert) { item in databaseAlert(item) } .alert(item: $alert) { item in databaseAlert(item) }
.fileImporter( .fileImporter(
isPresented: $showFileImporter, isPresented: $showFileImporter,
@@ -222,6 +245,15 @@ struct DatabaseView: View {
}, },
secondaryButton: .cancel() secondaryButton: .cancel()
) )
case .deleteFilesAndMedia:
return Alert(
title: Text("Delete files and media?"),
message: Text("This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain."),
primaryButton: .destructive(Text("Delete")) {
deleteFiles()
},
secondaryButton: .cancel()
)
case let .error(title, error): case let .error(title, error):
return Alert(title: Text(title), message: Text("\(error)")) return Alert(title: Text(title), message: Text("\(error)"))
} }
@@ -354,6 +386,11 @@ struct DatabaseView: View {
} }
} }
} }
private func deleteFiles() {
deleteAppFiles()
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
}
} }
struct DatabaseView_Previews: PreviewProvider { struct DatabaseView_Previews: PreviewProvider {

View File

@@ -15,6 +15,8 @@ private let maxItemSize: Int = 50000
struct TerminalView: View { struct TerminalView: View {
@EnvironmentObject var chatModel: ChatModel @EnvironmentObject var chatModel: ChatModel
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@State var composeState: ComposeState = ComposeState() @State var composeState: ComposeState = ComposeState()
@FocusState private var keyboardVisible: Bool @FocusState private var keyboardVisible: Bool
@State var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) @State var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
@@ -103,11 +105,19 @@ struct TerminalView: View {
func sendMessage() { func sendMessage() {
let cmd = ChatCommand.string(composeState.message) let cmd = ChatCommand.string(composeState.message)
DispatchQueue.global().async { if composeState.message.starts(with: "/sql") && (!prefPerformLA || !developerTools) {
Task { let resp = ChatResponse.chatCmdError(chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
composeState.inProgress = true DispatchQueue.main.async {
_ = await chatSendCmd(cmd) ChatModel.shared.terminalItems.append(.cmd(.now, cmd))
composeState.inProgress = false ChatModel.shared.terminalItems.append(.resp(.now, resp))
}
} else {
DispatchQueue.global().async {
Task {
composeState.inProgress = true
_ = await chatSendCmd(cmd)
composeState.inProgress = false
}
} }
} }
composeState = ComposeState() composeState = ComposeState()

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd"> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext"> <file original="en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.0" build-num="14A309"/>
</header> </header>
<body> <body>
<trans-unit id="&#10;" xml:space="preserve"> <trans-unit id="&#10;" xml:space="preserve">
@@ -82,6 +82,11 @@
<target>%lld contact(s) selected</target> <target>%lld contact(s) selected</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve">
<source>%lld file(s) with total size of %@</source>
<target>%lld file(s) with total size of %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve"> <trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source> <source>%lld members</source>
<target>%lld members</target> <target>%lld members</target>
@@ -701,6 +706,16 @@
<target>Delete database</target> <target>Delete database</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete files &amp; media" xml:space="preserve">
<source>Delete files &amp; media</source>
<target>Delete files &amp; media</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete files and media?" xml:space="preserve">
<source>Delete files and media?</source>
<target>Delete files and media?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete for everyone" xml:space="preserve"> <trans-unit id="Delete for everyone" xml:space="preserve">
<source>Delete for everyone</source> <source>Delete for everyone</source>
<target>Delete for everyone</target> <target>Delete for everyone</target>
@@ -1016,6 +1031,11 @@
<target>File: %@</target> <target>File: %@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Files" xml:space="preserve">
<source>Files</source>
<target>Files</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="For console" xml:space="preserve"> <trans-unit id="For console" xml:space="preserve">
<source>For console</source> <source>For console</source>
<target>For console</target> <target>For console</target>
@@ -1478,6 +1498,11 @@ We will be adding server redundancy to prevent lost messages.</target>
<target>Group not found!</target> <target>Group not found!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="No received or sent files" xml:space="preserve">
<source>No received or sent files</source>
<target>No received or sent files</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve"> <trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source> <source>Notifications</source>
<target>Notifications</target> <target>Notifications</target>
@@ -2103,6 +2128,11 @@ We will be adding server redundancy to prevent lost messages.</target>
<target>Theme</target> <target>Theme</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." xml:space="preserve">
<source>This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</source>
<target>This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve"> <trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
<source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source> <source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source>
<target>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</target> <target>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</target>
@@ -2995,7 +3025,7 @@ SimpleX servers cannot see your profile.</target>
</file> </file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext"> <file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.0" build-num="14A309"/>
</header> </header>
<body> <body>
<trans-unit id="CFBundleName" xml:space="preserve"> <trans-unit id="CFBundleName" xml:space="preserve">
@@ -3027,7 +3057,7 @@ SimpleX servers cannot see your profile.</target>
</file> </file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext"> <file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.0" build-num="14A309"/>
</header> </header>
<body> <body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve"> <trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,10 +3,10 @@
"project" : "SimpleX.xcodeproj", "project" : "SimpleX.xcodeproj",
"targetLocale" : "en", "targetLocale" : "en",
"toolInfo" : { "toolInfo" : {
"toolBuildNumber" : "13E113", "toolBuildNumber" : "14A309",
"toolID" : "com.apple.dt.xcode", "toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode", "toolName" : "Xcode",
"toolVersion" : "13.3" "toolVersion" : "14.0"
}, },
"version" : "1.0" "version" : "1.0"
} }

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd"> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="ru" datatype="plaintext"> <file original="en.lproj/Localizable.strings" source-language="en" target-language="ru" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.0" build-num="14A309"/>
</header> </header>
<body> <body>
<trans-unit id="&#10;" xml:space="preserve"> <trans-unit id="&#10;" xml:space="preserve">
@@ -82,6 +82,11 @@
<target>Выбрано контактов: %lld</target> <target>Выбрано контактов: %lld</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve">
<source>%lld file(s) with total size of %@</source>
<target>%lld файл(ов) общим размером %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve"> <trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source> <source>%lld members</source>
<target>Членов группы: %lld</target> <target>Членов группы: %lld</target>
@@ -701,6 +706,16 @@
<target>Удалить данные чата</target> <target>Удалить данные чата</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete files &amp; media" xml:space="preserve">
<source>Delete files &amp; media</source>
<target>Удалить файлы и медиа</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete files and media?" xml:space="preserve">
<source>Delete files and media?</source>
<target>Удалить файлы и медиа?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete for everyone" xml:space="preserve"> <trans-unit id="Delete for everyone" xml:space="preserve">
<source>Delete for everyone</source> <source>Delete for everyone</source>
<target>Удалить для всех</target> <target>Удалить для всех</target>
@@ -1016,6 +1031,11 @@
<target>Файл: %@</target> <target>Файл: %@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Files" xml:space="preserve">
<source>Files</source>
<target>Файлы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="For console" xml:space="preserve"> <trans-unit id="For console" xml:space="preserve">
<source>For console</source> <source>For console</source>
<target>Для консоли</target> <target>Для консоли</target>
@@ -1478,6 +1498,11 @@ We will be adding server redundancy to prevent lost messages.</source>
<target>Группа не найдена!</target> <target>Группа не найдена!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="No received or sent files" xml:space="preserve">
<source>No received or sent files</source>
<target>Нет полученных или отправленных файлов</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve"> <trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source> <source>Notifications</source>
<target>Уведомления</target> <target>Уведомления</target>
@@ -2103,6 +2128,11 @@ We will be adding server redundancy to prevent lost messages.</source>
<target>Тема</target> <target>Тема</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." xml:space="preserve">
<source>This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</source>
<target>Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve"> <trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
<source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source> <source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source>
<target>Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.</target> <target>Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.</target>
@@ -2995,7 +3025,7 @@ SimpleX серверы не могут получить доступ к ваше
</file> </file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext"> <file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.0" build-num="14A309"/>
</header> </header>
<body> <body>
<trans-unit id="CFBundleName" xml:space="preserve"> <trans-unit id="CFBundleName" xml:space="preserve">
@@ -3027,7 +3057,7 @@ SimpleX серверы не могут получить доступ к ваше
</file> </file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext"> <file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.0" build-num="14A309"/>
</header> </header>
<body> <body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve"> <trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,10 +3,10 @@
"project" : "SimpleX.xcodeproj", "project" : "SimpleX.xcodeproj",
"targetLocale" : "ru", "targetLocale" : "ru",
"toolInfo" : { "toolInfo" : {
"toolBuildNumber" : "13E113", "toolBuildNumber" : "14A309",
"toolID" : "com.apple.dt.xcode", "toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode", "toolName" : "Xcode",
"toolVersion" : "13.3" "toolVersion" : "14.0"
}, },
"version" : "1.0" "version" : "1.0"
} }

View File

@@ -1178,7 +1178,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71; CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1199,7 +1199,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = "4.0-beta.0"; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1220,7 +1220,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71; CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1241,7 +1241,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = "4.0-beta.0"; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1299,7 +1299,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71; CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1312,7 +1312,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = "4.0-beta.0"; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1329,7 +1329,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71; CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1342,7 +1342,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = "4.0-beta.0"; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1384,7 +1384,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
MARKETING_VERSION = "4.0-beta.0"; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1430,7 +1430,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
MARKETING_VERSION = "4.0-beta.0"; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;

View File

@@ -61,6 +61,46 @@ func fileModificationDate(_ path: String) -> Date? {
} }
} }
public func deleteAppFiles() {
let fm = FileManager.default
do {
let fileNames = try fm.contentsOfDirectory(atPath: getAppFilesDirectory().path)
for fileName in fileNames {
removeFile(fileName)
}
} catch {
logger.error("FileUtils deleteAppFiles error: \(error.localizedDescription)")
}
}
func fileSize(_ url: URL) -> Int? { // in bytes
do {
let val = try url.resourceValues(forKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey])
return val.totalFileAllocatedSize ?? val.fileAllocatedSize
} catch {
logger.error("FileUtils fileSize error: \(error.localizedDescription)")
return nil
}
}
public func directoryFileCountAndSize(_ dir: URL) -> (Int, Int)? { // size in bytes
let fm = FileManager.default
if let enumerator = fm.enumerator(at: dir, includingPropertiesForKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey], options: [], errorHandler: { (_, error) -> Bool in
logger.error("FileUtils directoryFileCountAndSize error: \(error.localizedDescription)")
return false
}) {
var fileCount = 0
var bytes = 0
for case let url as URL in enumerator {
fileCount += 1
bytes += fileSize(url) ?? 0
}
return (fileCount, bytes)
} else {
return nil
}
}
public func hasBackup(newerThan date: Date) -> Bool { public func hasBackup(newerThan date: Date) -> Bool {
let dbPath = getAppDatabasePath().path let dbPath = getAppDatabasePath().path
return hasBackupFile(dbPath + AGENT_DB_BAK, newerThan: date) return hasBackupFile(dbPath + AGENT_DB_BAK, newerThan: date)

View File

@@ -97,6 +97,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%lld contact(s) selected" = "Выбрано контактов: %lld"; "%lld contact(s) selected" = "Выбрано контактов: %lld";
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld файл(ов) общим размером %@";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%lld members" = "Членов группы: %lld"; "%lld members" = "Членов группы: %lld";
@@ -500,6 +503,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete database" = "Удалить данные чата"; "Delete database" = "Удалить данные чата";
/* No comment provided by engineer. */
"Delete files & media" = "Удалить файлы и медиа";
/* No comment provided by engineer. */
"Delete files and media?" = "Удалить файлы и медиа?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete for everyone" = "Удалить для всех"; "Delete for everyone" = "Удалить для всех";
@@ -713,6 +722,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"File: %@" = "Файл: %@"; "File: %@" = "Файл: %@";
/* No comment provided by engineer. */
"Files" = "Файлы";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"For console" = "Для консоли"; "For console" = "Для консоли";
@@ -1043,6 +1055,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"No group!" = "Группа не найдена!"; "No group!" = "Группа не найдена!";
/* No comment provided by engineer. */
"No received or sent files" = "Нет полученных или отправленных файлов";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Notifications" = "Уведомления"; "Notifications" = "Уведомления";
@@ -1457,6 +1472,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Theme" = "Тема"; "Theme" = "Тема";
/* No comment provided by engineer. */
"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны."; "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.";

View File

@@ -7,7 +7,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: a3f58fdc6b5b6bc135c6db4c3bc301869174854b tag: 41d3c14157bb621b7e6a01eddd14419ef6c24de3
source-repository-package source-repository-package
type: git type: git

View File

@@ -1,5 +1,5 @@
name: simplex-chat name: simplex-chat
version: 3.2.1 version: 4.0.0
#synopsis: #synopsis:
#description: #description:
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme

View File

@@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."a3f58fdc6b5b6bc135c6db4c3bc301869174854b" = "045fj37vdwi31fda2g7y9dzc7jzqwil1x705c71mgg8pnzmsphr7"; "https://github.com/simplex-chat/simplexmq.git"."41d3c14157bb621b7e6a01eddd14419ef6c24de3" = "1phdb4y6vrja6ina3j2jks4a7xwb6sak9wg8kr3b1vp01bgdrjzg";
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd"; "https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";
"https://github.com/simplex-chat/sqlcipher-simple.git"."5e154a2aeccc33ead6c243ec07195ab673137221" = "1d1gc5wax4vqg0801ajsmx1sbwvd9y7p7b8mmskvqsmpbwgbh0m0"; "https://github.com/simplex-chat/sqlcipher-simple.git"."5e154a2aeccc33ead6c243ec07195ab673137221" = "1d1gc5wax4vqg0801ajsmx1sbwvd9y7p7b8mmskvqsmpbwgbh0m0";
"https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp"; "https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";

View File

@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: simplex-chat name: simplex-chat
version: 3.2.1 version: 4.0.0
category: Web, System, Services, Cryptography category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat author: simplex.chat

View File

@@ -259,6 +259,15 @@ processChatCommand = \case
setActive $ ActiveC c setActive $ ActiveC c
pure . CRNewChatItem $ AChatItem SCTDirect SMDSnd (DirectChat ct) ci pure . CRNewChatItem $ AChatItem SCTDirect SMDSnd (DirectChat ct) ci
where where
-- This method creates file invitation without connection request - it has to be accepted with x.acpt.file.inv message sent back to the contact
-- setupSndFileTransfer' :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd))
-- setupSndFileTransfer' ct = forM file_ $ \file -> do
-- (fileSize, chSize) <- checkSndFile file
-- let fileName = takeFileName file
-- fileInvitation = FileInvitation {fileName, fileSize, fileConnReq = Nothing}
-- fileId <- withStore' $ \db -> createSndDirectFileTransfer db userId ct file fileInvitation chSize
-- let ciFile = CIFile {fileId, fileName, fileSize, filePath = Just file, fileStatus = CIFSSndStored}
-- pure (fileInvitation, ciFile)
setupSndFileTransfer :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd)) setupSndFileTransfer :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd))
setupSndFileTransfer ct = forM file_ $ \file -> do setupSndFileTransfer ct = forM file_ $ \file -> do
(fileSize, chSize) <- checkSndFile file (fileSize, chSize) <- checkSndFile file
@@ -1121,30 +1130,37 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, fileInvitation = F
_ -> throwChatError $ CEFileAlreadyReceiving fName _ -> throwChatError $ CEFileAlreadyReceiving fName
case fileConnReq of case fileConnReq of
-- direct file protocol -- direct file protocol
Just connReq -> Just connReq -> do
-- [async agent commands] keep command synchronous, but process error -- [async agent commands] keep command synchronous, but process error
tryError (withAgent $ \a -> joinConnection a True connReq . directMessage $ XFileAcpt fName) >>= \case agentConnId <- withAgent $ \a -> joinConnection a True connReq . directMessage $ XFileAcpt fName
Right agentConnId -> do filePath <- getRcvFilePath filePath_ fName
filePath <- getRcvFilePath filePath_ fName withStore $ \db -> acceptRcvFileTransfer db user fileId agentConnId ConnJoined filePath
withStore $ \db -> acceptRcvFileTransfer db user fileId agentConnId ConnJoined filePath -- group & direct file protocol
Left e -> throwError e Nothing -> do
-- group file protocol chatRef <- withStore $ \db -> getChatRefByFileId db user fileId
Nothing -> case (chatRef, grpMemberId) of
case grpMemberId of (ChatRef CTDirect contactId, Nothing) -> do
Nothing -> throwChatError $ CEFileInternal "group member not found for file transfer" ct <- withStore $ \db -> getContact db userId contactId
Just memId -> do (msg, ci) <- acceptFile
(GroupInfo {groupId}, GroupMember {activeConn}) <- withStore $ \db -> getGroupAndMember db user memId void $ sendDirectContactMessage ct msg
pure ci
(ChatRef CTGroup groupId, Just memId) -> do
GroupMember {activeConn} <- withStore $ \db -> getGroupMember db user groupId memId
case activeConn of case activeConn of
Just conn -> do Just conn -> do
sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId (msg, ci) <- acceptFile
-- [async agent commands] keep command synchronous, but process error void $ sendDirectMessage conn msg $ GroupId groupId
(agentConnId, fileInvConnReq) <- withAgent $ \a -> createConnection a True SCMInvitation
filePath <- getRcvFilePath filePath_ fName
ci <- withStore $ \db -> acceptRcvFileTransfer db user fileId agentConnId ConnNew filePath
void $ sendDirectMessage conn (XFileAcptInv sharedMsgId fileInvConnReq fName) (GroupId groupId)
pure ci pure ci
_ -> throwChatError $ CEFileInternal "member connection not active" _ -> throwChatError $ CEFileInternal "member connection not active"
_ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer"
where where
acceptFile :: m (ChatMsgEvent, AChatItem)
acceptFile = do
sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId
(agentConnId, fileInvConnReq) <- withAgent $ \a -> createConnection a True SCMInvitation
filePath <- getRcvFilePath filePath_ fName
ci <- withStore (\db -> acceptRcvFileTransfer db user fileId agentConnId ConnNew filePath)
pure (XFileAcptInv sharedMsgId fileInvConnReq fName, ci)
getRcvFilePath :: Maybe FilePath -> String -> m FilePath getRcvFilePath :: Maybe FilePath -> String -> m FilePath
getRcvFilePath fPath_ fn = case fPath_ of getRcvFilePath fPath_ fn = case fPath_ of
Nothing -> Nothing ->
@@ -1416,6 +1432,7 @@ processAgentMessage (Just user@User {userId, profile}) corrId agentConnId agentM
-- TODO discontinue XFile -- TODO discontinue XFile
XFile fInv -> processFileInvitation' ct fInv msg msgMeta XFile fInv -> processFileInvitation' ct fInv msg msgMeta
XFileCancel sharedMsgId -> xFileCancel ct sharedMsgId msgMeta XFileCancel sharedMsgId -> xFileCancel ct sharedMsgId msgMeta
XFileAcptInv sharedMsgId fileConnReq fName -> xFileAcptInv ct sharedMsgId fileConnReq fName msgMeta
XInfo p -> xInfo ct p XInfo p -> xInfo ct p
XGrpInv gInv -> processGroupInvitation ct gInv msg msgMeta XGrpInv gInv -> processGroupInvitation ct gInv msg msgMeta
XInfoProbe probe -> xInfoProbe ct probe XInfoProbe probe -> xInfoProbe ct probe
@@ -1956,6 +1973,18 @@ processAgentMessage (Just user@User {userId, profile}) corrId agentConnId agentM
cancelRcvFileTransfer user ft cancelRcvFileTransfer user ft
toView $ CRRcvFileSndCancelled ft toView $ CRRcvFileSndCancelled ft
xFileAcptInv :: Contact -> SharedMsgId -> ConnReqInvitation -> String -> MsgMeta -> m ()
xFileAcptInv ct sharedMsgId fileConnReq fName msgMeta = do
checkIntegrityCreateItem (CDDirectRcv ct) msgMeta
fileId <- withStore $ \db -> getDirectFileIdBySharedMsgId db user ct sharedMsgId
(FileTransferMeta {fileName, cancelled}, _) <- withStore (\db -> getSndFileTransfer db user fileId)
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
if fName == fileName
then unless cancelled $ do
connIds <- joinAgentConnectionAsync user True fileConnReq $ directMessage XOk
withStore' $ \db -> createSndDirectFTConnection db user fileId connIds
else messageError "x.file.acpt.inv: fileName is different from expected"
xFileCancelGroup :: GroupInfo -> GroupMember -> SharedMsgId -> MsgMeta -> m () xFileCancelGroup :: GroupInfo -> GroupMember -> SharedMsgId -> MsgMeta -> m ()
xFileCancelGroup g@GroupInfo {groupId} mem@GroupMember {memberId} sharedMsgId msgMeta = do xFileCancelGroup g@GroupInfo {groupId} mem@GroupMember {memberId} sharedMsgId msgMeta = do
checkIntegrityCreateItem (CDGroupRcv g mem) msgMeta checkIntegrityCreateItem (CDGroupRcv g mem) msgMeta
@@ -1977,17 +2006,12 @@ processAgentMessage (Just user@User {userId, profile}) corrId agentConnId agentM
checkIntegrityCreateItem (CDGroupRcv g m) msgMeta checkIntegrityCreateItem (CDGroupRcv g m) msgMeta
fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
(FileTransferMeta {fileName, cancelled}, _) <- withStore (\db -> getSndFileTransfer db user fileId) (FileTransferMeta {fileName, cancelled}, _) <- withStore (\db -> getSndFileTransfer db user fileId)
unless cancelled $ if fName == fileName
if fName == fileName then unless cancelled $ do
then -- [async agent commands] no continuation needed, but command should be asynchronous for stability
tryError connIds <- joinAgentConnectionAsync user True fileConnReq $ directMessage XOk
-- [async agent commands] no continuation needed, but command should be asynchronous for stability withStore' $ \db -> createSndGroupFileTransferConnection db user fileId connIds m
(joinAgentConnectionAsync user True fileConnReq . directMessage $ XOk) else messageError "x.file.acpt.inv: fileName is different from expected"
>>= \case
Right connIds ->
withStore' $ \db -> createSndGroupFileTransferConnection db user fileId connIds m
Left e -> throwError e
else messageError "x.file.acpt.inv: fileName is different from expected"
groupMsgToView :: GroupInfo -> GroupMember -> ChatItem 'CTGroup 'MDRcv -> MsgMeta -> m () groupMsgToView :: GroupInfo -> GroupMember -> ChatItem 'CTGroup 'MDRcv -> MsgMeta -> m ()
groupMsgToView gInfo m ci msgMeta = do groupMsgToView gInfo m ci msgMeta = do

View File

@@ -106,6 +106,8 @@ module Simplex.Chat.Store
matchSentProbe, matchSentProbe,
mergeContactRecords, mergeContactRecords,
createSndFileTransfer, createSndFileTransfer,
createSndDirectFileTransfer,
createSndDirectFTConnection,
createSndGroupFileTransfer, createSndGroupFileTransfer,
createSndGroupFileTransferConnection, createSndGroupFileTransferConnection,
updateFileCancelled, updateFileCancelled,
@@ -113,6 +115,7 @@ module Simplex.Chat.Store
getSharedMsgIdByFileId, getSharedMsgIdByFileId,
getFileIdBySharedMsgId, getFileIdBySharedMsgId,
getGroupFileIdBySharedMsgId, getGroupFileIdBySharedMsgId,
getDirectFileIdBySharedMsgId,
getChatRefByFileId, getChatRefByFileId,
updateSndFileStatus, updateSndFileStatus,
createSndFileChunk, createSndFileChunk,
@@ -2001,6 +2004,25 @@ createSndFileTransfer db userId Contact {contactId} filePath FileInvitation {fil
(fileId, fileStatus, connId, currentTs, currentTs) (fileId, fileStatus, connId, currentTs, currentTs)
pure fileId pure fileId
createSndDirectFileTransfer :: DB.Connection -> UserId -> Contact -> FilePath -> FileInvitation -> Integer -> IO Int64
createSndDirectFileTransfer db userId Contact {contactId} filePath FileInvitation {fileName, fileSize} chunkSize = do
currentTs <- getCurrentTime
DB.execute
db
"INSERT INTO files (user_id, contact_id, file_name, file_path, file_size, chunk_size, ci_file_status, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)"
(userId, contactId, fileName, filePath, fileSize, chunkSize, CIFSSndStored, currentTs, currentTs)
insertedRowId db
createSndDirectFTConnection :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> IO ()
createSndDirectFTConnection db user@User {userId} fileId (cmdId, acId) = do
currentTs <- getCurrentTime
Connection {connId} <- createSndFileConnection_ db userId fileId acId
setCommandConnId db user cmdId connId
DB.execute
db
"INSERT INTO snd_files (file_id, file_status, connection_id, created_at, updated_at) VALUES (?,?,?,?,?)"
(fileId, FSAccepted, connId, currentTs, currentTs)
createSndGroupFileTransfer :: DB.Connection -> UserId -> GroupInfo -> FilePath -> FileInvitation -> Integer -> IO Int64 createSndGroupFileTransfer :: DB.Connection -> UserId -> GroupInfo -> FilePath -> FileInvitation -> Integer -> IO Int64
createSndGroupFileTransfer db userId GroupInfo {groupId} filePath FileInvitation {fileName, fileSize} chunkSize = do createSndGroupFileTransfer db userId GroupInfo {groupId} filePath FileInvitation {fileName, fileSize} chunkSize = do
currentTs <- getCurrentTime currentTs <- getCurrentTime
@@ -2069,6 +2091,19 @@ getGroupFileIdBySharedMsgId db userId groupId sharedMsgId =
|] |]
(userId, groupId, sharedMsgId) (userId, groupId, sharedMsgId)
getDirectFileIdBySharedMsgId :: DB.Connection -> User -> Contact -> SharedMsgId -> ExceptT StoreError IO Int64
getDirectFileIdBySharedMsgId db User {userId} Contact {contactId} sharedMsgId =
ExceptT . firstRow fromOnly (SEFileIdNotFoundBySharedMsgId sharedMsgId) $
DB.query
db
[sql|
SELECT f.file_id
FROM files f
JOIN chat_items i ON i.chat_item_id = f.chat_item_id
WHERE i.user_id = ? AND i.contact_id = ? AND i.shared_msg_id = ?
|]
(userId, contactId, sharedMsgId)
getChatRefByFileId :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO ChatRef getChatRefByFileId :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO ChatRef
getChatRefByFileId db User {userId} fileId = getChatRefByFileId db User {userId} fileId =
liftIO getChatRef >>= \case liftIO getChatRef >>= \case

View File

@@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq # - ../simplexmq
- github: simplex-chat/simplexmq - github: simplex-chat/simplexmq
commit: a3f58fdc6b5b6bc135c6db4c3bc301869174854b commit: 41d3c14157bb621b7e6a01eddd14419ef6c24de3
# - ../direct-sqlcipher # - ../direct-sqlcipher
- github: simplex-chat/direct-sqlcipher - github: simplex-chat/direct-sqlcipher
commit: 34309410eb2069b029b8fc1872deb1e0db123294 commit: 34309410eb2069b029b8fc1872deb1e0db123294