Keep database reset atomic and owned beyond the activity lifecycle
This commit is contained in:
1 parent
665137314a
commit
791ff42547
3 files changed
+31
-26
No files matched your search
@@ -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<String> = 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
|
||||
|
||||
@@ -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) } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ── */
|
||||
|
||||
Reference in new issue
Block a user