From 61bdf4a6dfc12e7fa8961fbdda74950c59637768 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 10:57:28 +0100 Subject: [PATCH] Retain observed settings through read failures and retry without default resets --- .../java/com/utun/chat/data/SettingsOperations.kt | 12 ++++++++++++ .../com/utun/chat/data/SettingsOperationsTest.kt | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/SettingsOperations.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/SettingsOperations.kt index eb656eec..86e8d560 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/SettingsOperations.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/SettingsOperations.kt @@ -7,8 +7,11 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -37,4 +40,13 @@ class SettingsOperations(private val scope: CoroutineScope = CoroutineScope(Supe } fun dismissFailure() { _failed.value = false } suspend fun awaitIdle() { mutex.withLock { } } + + /** Ошибка чтения не публикует defaults поверх последнего значения и не завершает подписку UI. */ + fun observe(name: String, values: Flow): Flow = values.retryWhen { error, attempt -> + if (error is CancellationException || error !is Exception) false else { + reportFailure(name, error) + delay((250L shl attempt.toInt().coerceIn(0, 7)).coerceAtMost(30_000)) + true + } + } } diff --git a/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/SettingsOperationsTest.kt b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/SettingsOperationsTest.kt index fcc78822..831de2d2 100644 --- a/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/SettingsOperationsTest.kt +++ b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/SettingsOperationsTest.kt @@ -6,6 +6,9 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toList import org.junit.Assert.* import org.junit.Test @@ -39,4 +42,13 @@ class SettingsOperationsTest { val waiter = launch { owner.awaitIdle() }; waiter.cancel() gate.complete(Unit); change.await(); assertTrue(saved); assertFalse(change.isCancelled) } + @Test fun failedPreferenceObservationPreservesTheLastValueAndCanRecover() = runTest { + val owner = SettingsOperations(this) { _, _ -> }; var attempts = 0 + val values = flow { + if (++attempts == 1) { emit(42); throw java.io.IOException("read failed") } + emit(43) + } + assertEquals(listOf(42, 43), owner.observe("preferences", values).take(2).toList()) + assertTrue(owner.failed.value); assertEquals(2, attempts) + } }