From 8089a8c7ef9843cd303820b0ae1d5b5d70543608 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 10 Dec 2025 09:51:03 +0000 Subject: [PATCH] desktop: fix linking mobile and connecting after onboarding. Also fixes other cases when core controller changes in android/desktop. (#6489) --- .../chat/simplex/common/model/CryptoFile.kt | 4 +- .../chat/simplex/common/model/SimpleXAPI.kt | 37 ++++++++++++++----- .../chat/simplex/common/platform/Core.kt | 5 ++- .../chat/simplex/common/ui/theme/Theme.kt | 3 +- .../common/views/chatlist/UserPicker.kt | 2 +- .../common/views/database/DatabaseView.kt | 3 +- .../common/views/localauth/LocalAuthView.kt | 4 +- .../common/views/migration/MigrateToDevice.kt | 2 +- 8 files changed, 40 insertions(+), 20 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt index 28b46f592d..6ef56a9124 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt @@ -21,7 +21,7 @@ sealed class WriteFileResult { * */ fun writeCryptoFile(path: String, data: ByteArray): CryptoFileArgs { - val ctrl = ChatController.ctrl ?: throw Exception("Controller is not initialized") + val ctrl = ChatController.getChatCtrl() ?: throw Exception("Controller is not initialized") val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.rewind() @@ -44,7 +44,7 @@ fun readCryptoFile(path: String, cryptoArgs: CryptoFileArgs): ByteArray { } fun encryptCryptoFile(fromPath: String, toPath: String): CryptoFileArgs { - val ctrl = ChatController.ctrl ?: throw Exception("Controller is not initialized") + val ctrl = ChatController.getChatCtrl() ?: throw Exception("Controller is not initialized") val str = chatEncryptFile(ctrl, fromPath, toPath) val d = json.decodeFromString(WriteFileResult.serializer(), str) return when (d) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 09aae9a7d3..6a80e50285 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -482,17 +482,26 @@ class AppPreferences { private const val MESSAGE_TIMEOUT: Int = 300_000_000 object ChatController { - var ctrl: ChatCtrl? = -1 + private var chatCtrl: ChatCtrl? = -1 val appPrefs: AppPreferences by lazy { AppPreferences() } val messagesChannel: Channel = Channel() val chatModel = ChatModel - private var receiverStarted = false + private var receiverJob: Job? = null var lastMsgReceivedTimestamp: Long = System.currentTimeMillis() private set - fun hasChatCtrl() = ctrl != -1L && ctrl != null + fun hasChatCtrl() = chatCtrl != -1L && chatCtrl != null + + fun getChatCtrl(): ChatCtrl? = chatCtrl + + fun setChatCtrl(ctrl: ChatCtrl?) { + val wasRunning = receiverJob != null + stopReceiver() + chatCtrl = ctrl + if (wasRunning && ctrl != null) startReceiver() + } suspend fun getAgentSubsTotal(rh: Long?): Pair? { val userId = currentUserId("getAgentSubsTotal") @@ -639,17 +648,16 @@ object ChatController { private fun startReceiver() { Log.d(TAG, "ChatController startReceiver") - if (receiverStarted) return - receiverStarted = true - CoroutineScope(Dispatchers.IO).launch { + if (receiverJob != null || chatCtrl == null) return + receiverJob = CoroutineScope(Dispatchers.IO).launch { var releaseLock: (() -> Unit) = {} - while (true) { + while (isActive) { /** Global [ctrl] can be null. It's needed for having the same [ChatModel] that already made in [ChatController] without the need * to change it everywhere in code after changing a database. * Since it can be changed in background thread, making this check to prevent NullPointerException */ - val ctrl = ctrl + val ctrl = chatCtrl if (ctrl == null) { - receiverStarted = false + stopReceiver() break } try { @@ -689,6 +697,15 @@ object ChatController { } } + private fun stopReceiver() { + Log.d(TAG, "ChatController stopReceiver") + val job = receiverJob + if (job != null) { + receiverJob = null + job.cancel() + } + } + private suspend fun sendCmdWithRetry(rhId: Long?, cmd: CC, inProgress: MutableState? = null, retryNum: Int = 0): API? { val r = sendCmd(rhId, cmd, retryNum = retryNum) val alert = if (r is API.Error) retryableNetworkErrorAlert(r.err) else null @@ -773,7 +790,7 @@ object ChatController { } suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, retryNum: Int = 0, log: Boolean = true): API { - val ctrl = otherCtrl ?: ctrl ?: throw Exception("Controller is not initialized") + val ctrl = otherCtrl ?: chatCtrl ?: throw Exception("Controller is not initialized") return withContext(Dispatchers.IO) { val c = cmd.cmdString diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 959e4749dc..d0ce703033 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -56,6 +56,7 @@ fun initChatControllerOnStart() { } suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: () -> CompletableDeferred = { CompletableDeferred(true) }) { + Log.d(TAG, "initChatController") try { if (chatModel.ctrlInitInProgress.value) return chatModel.ctrlInitInProgress.value = true @@ -92,7 +93,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat val ctrl = if (res is DBMigrationResult.OK) { migrated[1] as Long } else null - chatController.ctrl = ctrl + chatController.setChatCtrl(ctrl) chatModel.chatDbEncrypted.value = dbKey != "" chatModel.chatDbStatus.value = res if (res != DBMigrationResult.OK) { @@ -206,7 +207,7 @@ fun chatInitControllerRemovingDatabases() { }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } val ctrl = migrated[1] as Long - chatController.ctrl = ctrl + chatController.setChatCtrl(ctrl) // We need only controller, not databases File(dbPath + "_chat.db").delete() File(dbPath + "_agent.db").delete() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index 01e19ea478..df9af7fbf6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -804,7 +804,8 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { LocalAppColors provides rememberedAppColors, LocalAppWallpaper provides rememberedWallpaper, LocalDensity provides density, - content = content) + content = content + ) } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 13351a2111..ed74e083e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -131,7 +131,7 @@ fun UserPicker( } LaunchedEffect(Unit) { // Controller.ctrl can be null when self-destructing activates - if (controller.ctrl != null && controller.ctrl != -1L) { + if (controller.hasChatCtrl()) { withBGApi { controller.reloadRemoteHosts() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 4a911fa6f0..d55d89f26b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -366,6 +366,7 @@ fun startChat( chatDbChanged: MutableState, progressIndicator: MutableState? = null ) { + Log.d(TAG, "startChat") withLongRunningApi { try { progressIndicator?.value = true @@ -532,7 +533,7 @@ fun deleteChatDatabaseFilesAndState() { appPrefs.newDatabaseInitialized.set(false) chatModel.desktopOnboardingRandomPassword.value = false controller.appPrefs.storeDBPassphrase.set(true) - controller.ctrl = null + controller.setChatCtrl(null) // Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself chatModel.chatId.value = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 8021a605db..f86edb0388 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -33,7 +33,7 @@ fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) { } } else { val r: LAResult = if (passcode.value == authRequest.password) { - if (authRequest.selfDestruct && sdPassword != null && controller.ctrl == -1L) { + if (authRequest.selfDestruct && sdPassword != null && controller.getChatCtrl() == -1L) { initChatControllerOnStart() } LAResult.Success @@ -58,7 +58,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( if (m.chatRunning.value == true) { stopChatAsync(m) } - val ctrl = m.controller.ctrl + val ctrl = m.controller.getChatCtrl() if (ctrl != null && ctrl != -1L) { /** * The following sequence can bring a user here: diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index d74846f8a3..6199621c39 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -633,7 +633,7 @@ private fun MutableState.startDownloading( private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { withLongRunningApi { try { - if (ChatController.ctrl == null || ChatController.ctrl == -1L) { + if (!ChatController.hasChatCtrl()) { chatInitControllerRemovingDatabases() } controller.apiDeleteStorage()