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"
minSdk 29
targetSdk 32
versionCode 53
versionName "4.0-beta.0"
versionCode 54
versionName "4.0-beta.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {

View File

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

View File

@@ -46,14 +46,7 @@ fun TerminalView(chatModel: ChatModel, close: () -> Unit) {
TerminalLayout(
chatModel.terminalItems,
composeState,
sendCommand = {
withApi {
// show "in progress"
chatModel.controller.sendCmd(CC.Console(composeState.value.message))
composeState.value = ComposeState(useLinkPreviews = false)
// hide "in progress"
}
},
sendCommand = { sendCommand(chatModel, composeState) },
close
)
} 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
fun TerminalLayout(
terminalItems: List<TerminalItem>,

View File

@@ -62,6 +62,7 @@ fun DatabaseView(
importArchiveAlert(m, context, uri, progressIndicator)
}
}
val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(getAppFilesDirectory(context))) }
LaunchedEffect(m.chatRunning) {
runChat.value = m.chatRunning.value ?: true
}
@@ -78,10 +79,12 @@ fun DatabaseView(
chatArchiveName,
chatArchiveTime,
chatLastStart,
appFilesCountAndSize,
startChat = { startChat(m, runChat, chatLastStart, m.chatDbChanged) },
stopChatAlert = { stopChatAlert(m, runChat, context) },
exportArchive = { exportArchive(context, m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) },
deleteChatAlert = { deleteChatAlert(m, progressIndicator) },
deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(context, appFilesCountAndSize) },
showSettingsModal
)
if (progressIndicator.value) {
@@ -112,10 +115,12 @@ fun DatabaseLayout(
chatArchiveName: MutableState<String?>,
chatArchiveTime: MutableState<Instant?>,
chatLastStart: MutableState<Instant?>,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
startChat: () -> Unit,
stopChatAlert: () -> Unit,
exportArchive: () -> Unit,
deleteChatAlert: () -> Unit,
deleteAppFilesAndMedia: () -> Unit,
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
) {
val stopped = !runChat
@@ -196,6 +201,28 @@ fun DatabaseLayout(
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) {
m.chatDbChanged.value = true
progressIndicator.value = false
@@ -527,10 +569,12 @@ fun PreviewDatabaseLayout() {
chatArchiveName = remember { mutableStateOf("dummy_archive") },
chatArchiveTime = remember { mutableStateOf(Clock.System.now()) },
chatLastStart = remember { mutableStateOf(Clock.System.now()) },
appFilesCountAndSize = remember { mutableStateOf(0 to 0L) },
startChat = {},
stopChatAlert = {},
exportArchive = {},
deleteChatAlert = {},
deleteAppFilesAndMedia = {},
showSettingsModal = { {} }
)
}

View File

@@ -24,8 +24,7 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.*
import androidx.core.content.FileProvider
import androidx.core.text.HtmlCompat
import chat.simplex.app.BuildConfig
import chat.simplex.app.SimplexApp
import chat.simplex.app.*
import chat.simplex.app.model.CIFile
import kotlinx.coroutines.*
import java.io.*
@@ -405,6 +404,31 @@ fun removeFile(context: Context, fileName: String): Boolean {
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 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="you_must_use_the_most_recent_version_of_database">Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе вы можете перестать получать сообщения от некоторых контактов.</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 -->
<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="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="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 -->
<string name="save_passphrase_in_keychain">Save passphrase in Keystore</string>

View File

@@ -17,6 +17,7 @@ enum DatabaseAlert: Identifiable {
case deleteChat
case chatDeleted
case deleteLegacyDatabase
case deleteFilesAndMedia
case error(title: LocalizedStringKey, error: String = "")
var id: String {
@@ -28,6 +29,7 @@ enum DatabaseAlert: Identifiable {
case .deleteChat: return "deleteChat"
case .chatDeleted: return "chatDeleted"
case .deleteLegacyDatabase: return "deleteLegacyDatabase"
case .deleteFilesAndMedia: return "deleteFilesAndMedia"
case let .error(title, _): return "error \(title)"
}
}
@@ -46,6 +48,7 @@ struct DatabaseView: View {
@State private var dbContainer = dbContainerGroupDefault.get()
@State private var legacyDatabase = hasLegacyDatabase()
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
@State private var appFilesCountAndSize: (Int, Int)?
var body: some View {
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) }
.fileImporter(
isPresented: $showFileImporter,
@@ -222,6 +245,15 @@ struct DatabaseView: View {
},
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):
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 {

View File

@@ -15,6 +15,8 @@ private let maxItemSize: Int = 50000
struct TerminalView: View {
@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()
@FocusState private var keyboardVisible: Bool
@State var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
@@ -103,11 +105,19 @@ struct TerminalView: View {
func sendMessage() {
let cmd = ChatCommand.string(composeState.message)
DispatchQueue.global().async {
Task {
composeState.inProgress = true
_ = await chatSendCmd(cmd)
composeState.inProgress = false
if composeState.message.starts(with: "/sql") && (!prefPerformLA || !developerTools) {
let resp = ChatResponse.chatCmdError(chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
DispatchQueue.main.async {
ChatModel.shared.terminalItems.append(.cmd(.now, cmd))
ChatModel.shared.terminalItems.append(.resp(.now, resp))
}
} else {
DispatchQueue.global().async {
Task {
composeState.inProgress = true
_ = await chatSendCmd(cmd)
composeState.inProgress = false
}
}
}
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">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
<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>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -82,6 +82,11 @@
<target>%lld contact(s) selected</target>
<note>No comment provided by engineer.</note>
</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">
<source>%lld members</source>
<target>%lld members</target>
@@ -701,6 +706,16 @@
<target>Delete database</target>
<note>No comment provided by engineer.</note>
</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">
<source>Delete for everyone</source>
<target>Delete for everyone</target>
@@ -1016,6 +1031,11 @@
<target>File: %@</target>
<note>No comment provided by engineer.</note>
</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">
<source>For console</source>
<target>For console</target>
@@ -1478,6 +1498,11 @@ We will be adding server redundancy to prevent lost messages.</target>
<target>Group not found!</target>
<note>No comment provided by engineer.</note>
</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">
<source>Notifications</source>
<target>Notifications</target>
@@ -2103,6 +2128,11 @@ We will be adding server redundancy to prevent lost messages.</target>
<target>Theme</target>
<note>No comment provided by engineer.</note>
</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">
<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>
@@ -2995,7 +3025,7 @@ SimpleX servers cannot see your profile.</target>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<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>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -3027,7 +3057,7 @@ SimpleX servers cannot see your profile.</target>
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<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>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,10 +3,10 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "en",
"toolInfo" : {
"toolBuildNumber" : "13E113",
"toolBuildNumber" : "14A309",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "13.3"
"toolVersion" : "14.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">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="ru" datatype="plaintext">
<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>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -82,6 +82,11 @@
<target>Выбрано контактов: %lld</target>
<note>No comment provided by engineer.</note>
</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">
<source>%lld members</source>
<target>Членов группы: %lld</target>
@@ -701,6 +706,16 @@
<target>Удалить данные чата</target>
<note>No comment provided by engineer.</note>
</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">
<source>Delete for everyone</source>
<target>Удалить для всех</target>
@@ -1016,6 +1031,11 @@
<target>Файл: %@</target>
<note>No comment provided by engineer.</note>
</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">
<source>For console</source>
<target>Для консоли</target>
@@ -1478,6 +1498,11 @@ We will be adding server redundancy to prevent lost messages.</source>
<target>Группа не найдена!</target>
<note>No comment provided by engineer.</note>
</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">
<source>Notifications</source>
<target>Уведомления</target>
@@ -2103,6 +2128,11 @@ We will be adding server redundancy to prevent lost messages.</source>
<target>Тема</target>
<note>No comment provided by engineer.</note>
</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">
<source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source>
<target>Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.</target>
@@ -2995,7 +3025,7 @@ SimpleX серверы не могут получить доступ к ваше
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
<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>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -3027,7 +3057,7 @@ SimpleX серверы не могут получить доступ к ваше
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
<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>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

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

View File

@@ -1178,7 +1178,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71;
CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@@ -1199,7 +1199,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "4.0-beta.0";
MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1220,7 +1220,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71;
CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@@ -1241,7 +1241,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "4.0-beta.0";
MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1299,7 +1299,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71;
CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@@ -1312,7 +1312,7 @@
"@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_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -1329,7 +1329,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71;
CURRENT_PROJECT_VERSION = 73;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@@ -1342,7 +1342,7 @@
"@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_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -1384,7 +1384,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Libraries/sim",
);
MARKETING_VERSION = "4.0-beta.0";
MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -1430,7 +1430,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Libraries/sim",
);
MARKETING_VERSION = "4.0-beta.0";
MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
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 {
let dbPath = getAppDatabasePath().path
return hasBackupFile(dbPath + AGENT_DB_BAK, newerThan: date)

View File

@@ -97,6 +97,9 @@
/* No comment provided by engineer. */
"%lld contact(s) selected" = "Выбрано контактов: %lld";
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld файл(ов) общим размером %@";
/* No comment provided by engineer. */
"%lld members" = "Членов группы: %lld";
@@ -500,6 +503,12 @@
/* No comment provided by engineer. */
"Delete database" = "Удалить данные чата";
/* No comment provided by engineer. */
"Delete files & media" = "Удалить файлы и медиа";
/* No comment provided by engineer. */
"Delete files and media?" = "Удалить файлы и медиа?";
/* No comment provided by engineer. */
"Delete for everyone" = "Удалить для всех";
@@ -713,6 +722,9 @@
/* No comment provided by engineer. */
"File: %@" = "Файл: %@";
/* No comment provided by engineer. */
"Files" = "Файлы";
/* No comment provided by engineer. */
"For console" = "Для консоли";
@@ -1043,6 +1055,9 @@
/* No comment provided by engineer. */
"No group!" = "Группа не найдена!";
/* No comment provided by engineer. */
"No received or sent files" = "Нет полученных или отправленных файлов";
/* No comment provided by engineer. */
"Notifications" = "Уведомления";
@@ -1457,6 +1472,9 @@
/* No comment provided by engineer. */
"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. */
"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
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: a3f58fdc6b5b6bc135c6db4c3bc301869174854b
tag: 41d3c14157bb621b7e6a01eddd14419ef6c24de3
source-repository-package
type: git

View File

@@ -1,5 +1,5 @@
name: simplex-chat
version: 3.2.1
version: 4.0.0
#synopsis:
#description:
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/sqlcipher-simple.git"."5e154a2aeccc33ead6c243ec07195ab673137221" = "1d1gc5wax4vqg0801ajsmx1sbwvd9y7p7b8mmskvqsmpbwgbh0m0";
"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
name: simplex-chat
version: 3.2.1
version: 4.0.0
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat

View File

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

View File

@@ -106,6 +106,8 @@ module Simplex.Chat.Store
matchSentProbe,
mergeContactRecords,
createSndFileTransfer,
createSndDirectFileTransfer,
createSndDirectFTConnection,
createSndGroupFileTransfer,
createSndGroupFileTransferConnection,
updateFileCancelled,
@@ -113,6 +115,7 @@ module Simplex.Chat.Store
getSharedMsgIdByFileId,
getFileIdBySharedMsgId,
getGroupFileIdBySharedMsgId,
getDirectFileIdBySharedMsgId,
getChatRefByFileId,
updateSndFileStatus,
createSndFileChunk,
@@ -2001,6 +2004,25 @@ createSndFileTransfer db userId Contact {contactId} filePath FileInvitation {fil
(fileId, fileStatus, connId, currentTs, currentTs)
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 userId GroupInfo {groupId} filePath FileInvitation {fileName, fileSize} chunkSize = do
currentTs <- getCurrentTime
@@ -2069,6 +2091,19 @@ getGroupFileIdBySharedMsgId db 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 User {userId} fileId =
liftIO getChatRef >>= \case

View File

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