diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt index 97dfd7e25..f070716af 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt @@ -92,10 +92,15 @@ object NativeLib { check(withContext(Dispatchers.IO) { nativeStop() }) { "native stop before database reset failed" } initialized = false withContext(Dispatchers.Main.immediate) { ChatApplication.instance.memberFiles.coreStopped(); afterStop() } - val cleared = withContext(Dispatchers.IO) { nativeClearDatabase() } - if (configText != null) check(withContext(Dispatchers.IO) { - initCallbacks(dbPath, controlPort) && nativeStart(configText) - }) { "native start after database reset failed" } + val cleared = withContext(Dispatchers.IO) { nativeClearDatabase(dbPath) } + if (configText != null) { + check(withContext(Dispatchers.Main.immediate) { com.utun.chat.headless.HeadlessService.isActive() }) { + "service stopped before database reset recovery" + } + check(withContext(Dispatchers.IO) { initCallbacks(dbPath, controlPort) && nativeStart(configText) }) { + "native start after database reset failed" + } + } coreStopping = false cleared } @@ -178,7 +183,6 @@ object NativeLib { fun getMessages(channelId: String, limit: Int = 50, anchor: String = "", exact: Boolean = false): String = checkNotNull(nativeGetMessages(channelId, limit, messageIdBytes(anchor), exact)) { "Message snapshot unavailable channel=$channelId" } fun feedbackCommand(json: String, requestId: Long = 0): Boolean = nativeFeedbackCommand(json.toByteArray(Charsets.UTF_8), requestId) - fun clearDatabase(): Boolean = nativeClearDatabase() fun setDebugLevel(category: String, level: String) { nativeSetDebugLevel(category, level) } fun getDebugCategories(): List = nativeGetDebugCategories()?.split(",")?.filter { it.isNotEmpty() } ?: emptyList() @@ -470,7 +474,7 @@ object NativeLib { private external fun nativeSubscribeLogs(enabled: Boolean) private external fun nativeSetUdpLogTarget(ip: String, port: Int) private external fun nativeRegenerateKeys(configText: String): Boolean - private external fun nativeClearDatabase(): Boolean + private external fun nativeClearDatabase(dbPath: String): Boolean private external fun nativeGetMyNodeId(): Long private external fun nativeRequestInviteCandidates(channelId: String): Boolean private external fun nativeRequestInviteLink(channelId: String, targetNodeId: Long, requestId: Long): Boolean diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index 7301f009d..27efda551 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -1894,32 +1894,29 @@ class ChatViewModel : ViewModel() { fun dismissDatabaseResetError() { _databaseReset.update { it.copy(failed = false) } } - /** Принятый из UI сброс живёт в ViewModel: смена вкладки/поворот экрана не отменяет восстановление ядра. */ + /** Принятый сброс принадлежит очереди Application, включая закрытие Activity; NativeLib сериализует stop/clear/start. */ fun resetDatabase() { if (_databaseReset.value.running || _generatingKeys.value) return + val retryCleanup = _databaseReset.value.failed && NativeLib.isCoreStopping() _databaseReset.value = DatabaseResetState(running = true) - viewModelScope.launch { + val provider = ChatApplication.instance.configProvider + provider.changes.submit("reset database") { try { - HeadlessService.awaitStartup() - withContext(Dispatchers.IO) { - val wasRunning = NativeLib.isReady() - NativeLib.stop() - withContext(Dispatchers.Main) { clearState() } - val cleared = NativeLib.clearDatabase() - if (!cleared) LogManager.addLog("ERROR", "CFG", "database reset rejected by native backend") - if (wasRunning) { - val provider = ChatApplication.instance.configProvider - val config = provider.buildConfigText().await() - check(NativeLib.restart(provider.getString("db.path") ?: "", provider.getInt("control.port"), config)) { - "restart after database reset rejected" - } - } - _databaseReset.update { it.copy(failed = !cleared) } + if (!retryCleanup) HeadlessService.awaitStartup() + val restore = (NativeLib.isReady() || retryCleanup) && withContext(Dispatchers.IO) { provider.getServiceEnabled() } + val path = withContext(Dispatchers.IO) { provider.getString("db.path") ?: ChatApplication.instance.filesDir.absolutePath } + val config = if (restore) provider.buildConfigText().await() else null + val cleared = NativeLib.resetDatabase(path, 9999, config, ::clearState) + if (!cleared) { + _databaseReset.update { it.copy(failed = true) } + throw java.io.IOException("Database cleanup failed; native diagnostic contains the affected paths") } + LogManager.addLog("INFO", "CFG", "Database reset completed directory=$path restart=$restore") } catch (e: kotlinx.coroutines.CancellationException) { throw e } catch (e: Exception) { LogManager.addLog("ERROR", "CFG", "database reset failed: ${e.javaClass.simpleName}: ${e.message}") _databaseReset.update { it.copy(failed = true) } + throw e } finally { _databaseReset.update { it.copy(running = false) } } } } diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index b4c859e64..2a0214cd2 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -2578,9 +2578,13 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRegenerateKey } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeClearDatabase( - JNIEnv* env, jobject thiz) { - (void)env; (void)thiz; - return utun_bridge_clear_database(g_db_path) == 0 ? JNI_TRUE : JNI_FALSE; + JNIEnv* env, jobject thiz, jstring directory) { + (void)thiz; + const char* path = jni_utf8(env, directory); + if (!path) return JNI_FALSE; + int result = utun_bridge_clear_database(path); + u_free((void*)path); + return result == 0 ? JNI_TRUE : JNI_FALSE; } /* ── Voice recording JNI ── */