fix: harden provider session restoration
This commit is contained in:
+21
-3
@@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import com.flagship.abox.manager.domain.DomainResult
|
||||
@@ -13,19 +14,34 @@ import com.flagship.abox.manager.domain.ProviderProfileId
|
||||
import com.flagship.abox.manager.domain.ProviderProfileStore
|
||||
import com.flagship.abox.manager.domain.ProviderType
|
||||
import com.flagship.abox.manager.domain.Username
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
|
||||
class ProviderProfileStoreImpl(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) : ProviderProfileStore {
|
||||
override fun observeProfiles(): Flow<List<ProviderProfile>> = dataStore.data.map(::profilesFrom)
|
||||
private val safeData: Flow<Preferences>
|
||||
get() = dataStore.data
|
||||
.retryWhen { cause, attempt ->
|
||||
val retry = cause is IOException && attempt < MAX_READ_RETRIES
|
||||
if (retry) delay(READ_RETRY_DELAY_MILLIS)
|
||||
retry
|
||||
}
|
||||
.catch { error ->
|
||||
if (error is IOException) emit(emptyPreferences()) else throw error
|
||||
}
|
||||
|
||||
override fun observeActiveProfile(): Flow<ProviderProfile?> = dataStore.data.map(::activeProfileFrom)
|
||||
override fun observeProfiles(): Flow<List<ProviderProfile>> = safeData.map(::profilesFrom)
|
||||
|
||||
override fun observeActiveProfile(): Flow<ProviderProfile?> = safeData.map(::activeProfileFrom)
|
||||
|
||||
override fun observeLoginPreferences(profileId: ProviderProfileId): Flow<LoginPreferences> =
|
||||
dataStore.data.map { preferencesFrom(it, profileId) }
|
||||
safeData.map { preferencesFrom(it, profileId) }
|
||||
|
||||
override suspend fun profiles(): List<ProviderProfile> = profilesFrom(dataStore.data.first())
|
||||
|
||||
@@ -99,6 +115,8 @@ class ProviderProfileStoreImpl(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_READ_RETRIES = 2L
|
||||
private const val READ_RETRY_DELAY_MILLIS = 50L
|
||||
private val PROFILE_IDS = stringSetPreferencesKey("profile_ids")
|
||||
private val ACTIVE_PROFILE_ID = stringPreferencesKey("active_profile_id")
|
||||
|
||||
|
||||
+3
@@ -2,11 +2,14 @@ package com.flagship.abox.manager.session.profile
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
|
||||
private const val SESSION_DATA_STORE_NAME = "provider_session"
|
||||
|
||||
val Context.sessionDataStore: DataStore<Preferences> by preferencesDataStore(
|
||||
name = SESSION_DATA_STORE_NAME,
|
||||
corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() },
|
||||
)
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.flagship.abox.manager.session
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import com.flagship.abox.manager.session.profile.ProviderProfileStoreImpl
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ProviderProfileStoreFailureTest {
|
||||
@Test
|
||||
fun ioFailureFallsBackToEmptyPreferences() = runTest {
|
||||
val store = ProviderProfileStoreImpl(FailingReadDataStore())
|
||||
|
||||
assertEquals(emptyList<Any>(), store.observeProfiles().first())
|
||||
assertEquals(null, store.observeActiveProfile().first())
|
||||
}
|
||||
|
||||
private class FailingReadDataStore : DataStore<Preferences> {
|
||||
override val data: Flow<Preferences> = flow {
|
||||
throw IOException("disk unavailable")
|
||||
}
|
||||
|
||||
override suspend fun updateData(
|
||||
transform: suspend (t: Preferences) -> Preferences,
|
||||
): Preferences = transform(emptyPreferences())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user