Add storage for the last sync date (#732)

This commit is contained in:
David Perez 2024-01-23 13:15:40 -06:00 committed by Álison Fernandes
parent c3cb61e43a
commit bc1f5cb020
4 changed files with 96 additions and 0 deletions

View file

@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import kotlinx.coroutines.flow.Flow
import java.time.Instant
/**
* Primary access point for general settings-related disk information.
@ -41,6 +42,23 @@ interface SettingsDiskSource {
*/
fun clearData(userId: String)
/**
* Gets the last time the app synced the vault data for a given [userId] (or `null` if the
* vault has never been synced).
*/
fun getLastSyncTime(userId: String): Instant?
/**
* Emits updates that track [getLastSyncTime] for the given [userId]. This will replay the
* last known value, if any.
*/
fun getLastSyncTimeFlow(userId: String): Flow<Instant?>
/**
* Stores the given [lastSyncTime] for the given [userId].
*/
fun storeLastSyncTime(userId: String, lastSyncTime: Instant?)
/**
* Gets the current vault timeout (in minutes) for the given [userId] (or `null` if the vault
* should never time out).

View file

@ -11,12 +11,14 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.Instant
private const val APP_LANGUAGE_KEY = "$BASE_KEY:appLocale"
private const val APP_THEME_KEY = "$BASE_KEY:theme"
private const val PULL_TO_REFRESH_KEY = "$BASE_KEY:syncOnRefresh"
private const val INLINE_AUTOFILL_ENABLED_KEY = "$BASE_KEY:inlineAutofillEnabled"
private const val BLOCKED_AUTOFILL_URIS_KEY = "$BASE_KEY:autofillBlacklistedUris"
private const val VAULT_LAST_SYNC_TIME = "$BASE_KEY:vaultLastSyncTime"
private const val VAULT_TIMEOUT_ACTION_KEY = "$BASE_KEY:vaultTimeoutAction"
private const val VAULT_TIME_IN_MINUTES_KEY = "$BASE_KEY:vaultTimeout"
private const val DISABLE_ICON_LOADING_KEY = "$BASE_KEY:disableFavicon"
@ -34,6 +36,8 @@ class SettingsDiskSourceImpl(
private val mutableAppThemeFlow =
bufferedMutableSharedFlow<AppTheme>(replay = 1)
private val mutableLastSyncFlowMap = mutableMapOf<String, MutableSharedFlow<Instant?>>()
private val mutableVaultTimeoutActionFlowMap =
mutableMapOf<String, MutableSharedFlow<VaultTimeoutAction?>>()
@ -97,8 +101,24 @@ class SettingsDiskSourceImpl(
userId = userId,
isApprovePasswordlessLoginsEnabled = null,
)
storeLastSyncTime(userId = userId, lastSyncTime = null)
}
override fun getLastSyncTime(userId: String): Instant? =
getLong(key = "${VAULT_LAST_SYNC_TIME}_$userId")?.let { Instant.ofEpochMilli(it) }
override fun storeLastSyncTime(userId: String, lastSyncTime: Instant?) {
putLong(
key = "${VAULT_LAST_SYNC_TIME}_$userId",
value = lastSyncTime?.toEpochMilli(),
)
getMutableLastSyncFlow(userId = userId).tryEmit(lastSyncTime)
}
override fun getLastSyncTimeFlow(userId: String): Flow<Instant?> =
getMutableLastSyncFlow(userId = userId)
.onSubscription { emit(getLastSyncTime(userId = userId)) }
override fun getVaultTimeoutInMinutes(userId: String): Int? =
getInt(key = "${VAULT_TIME_IN_MINUTES_KEY}_$userId")
@ -177,6 +197,13 @@ class SettingsDiskSourceImpl(
)
}
private fun getMutableLastSyncFlow(
userId: String,
): MutableSharedFlow<Instant?> =
mutableLastSyncFlowMap.getOrPut(userId) {
bufferedMutableSharedFlow(replay = 1)
}
private fun getMutableVaultTimeoutActionFlow(
userId: String,
): MutableSharedFlow<VaultTimeoutAction?> =

View file

@ -13,6 +13,7 @@ import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.Instant
class SettingsDiskSourceTest {
private val fakeSharedPreferences = FakeSharedPreferences()
@ -89,6 +90,10 @@ class SettingsDiskSourceTest {
userId = userId,
isApprovePasswordlessLoginsEnabled = true,
)
settingsDiskSource.storeLastSyncTime(
userId = userId,
lastSyncTime = Instant.parse("2023-10-27T12:00:00Z"),
)
settingsDiskSource.clearData(userId = userId)
@ -98,6 +103,29 @@ class SettingsDiskSourceTest {
assertNull(settingsDiskSource.getInlineAutofillEnabled(userId = userId))
assertNull(settingsDiskSource.getBlockedAutofillUris(userId = userId))
assertNull(settingsDiskSource.getApprovePasswordlessLoginsEnabled(userId = userId))
assertNull(settingsDiskSource.getLastSyncTime(userId = userId))
}
@Test
fun `getLastSyncTime should pull from and update SharedPreferences`() {
val userId = "userId-1234"
val lastVaultSync = "bwPreferencesStorage:vaultLastSyncTime_$userId"
val instantLong = 1_698_408_000_000L
val instant = Instant.ofEpochMilli(instantLong)
// Assert that the default value in disk source is null
assertNull(settingsDiskSource.getLastSyncTime(userId = userId))
// Updating the shared preferences should update disk source.
fakeSharedPreferences.edit { putLong(lastVaultSync, instantLong) }
assertEquals(instant, settingsDiskSource.getLastSyncTime(userId = userId))
// Updating the disk source updates the shared preferences
settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = instant)
assertEquals(
fakeSharedPreferences.getLong(lastVaultSync, 0L),
instantLong,
)
}
@Test

View file

@ -8,6 +8,7 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.onSubscription
import java.time.Instant
/**
* Fake, memory-based implementation of [SettingsDiskSource].
@ -17,6 +18,8 @@ class FakeSettingsDiskSource : SettingsDiskSource {
private val mutableAppThemeFlow =
bufferedMutableSharedFlow<AppTheme>(replay = 1)
private val mutableLastSyncCallFlowMap = mutableMapOf<String, MutableSharedFlow<Instant?>>()
private val mutableVaultTimeoutActionsFlowMap =
mutableMapOf<String, MutableSharedFlow<VaultTimeoutAction?>>()
@ -30,6 +33,7 @@ class FakeSettingsDiskSource : SettingsDiskSource {
bufferedMutableSharedFlow<Boolean?>()
private var storedAppTheme: AppTheme = AppTheme.DEFAULT
private val storedLastSyncTime = mutableMapOf<String, Instant?>()
private val storedVaultTimeoutActions = mutableMapOf<String, VaultTimeoutAction?>()
private val storedVaultTimeoutInMinutes = mutableMapOf<String, Int?>()
@ -76,6 +80,18 @@ class FakeSettingsDiskSource : SettingsDiskSource {
mutableVaultTimeoutActionsFlowMap.remove(userId)
mutableVaultTimeoutInMinutesFlowMap.remove(userId)
mutableLastSyncCallFlowMap.remove(userId)
}
override fun getLastSyncTime(userId: String): Instant? = storedLastSyncTime[userId]
override fun getLastSyncTimeFlow(userId: String): Flow<Instant?> =
getMutableLastSyncTimeFlow(userId = userId)
.onSubscription { emit(getLastSyncTime(userId = userId)) }
override fun storeLastSyncTime(userId: String, lastSyncTime: Instant?) {
storedLastSyncTime[userId] = lastSyncTime
getMutableLastSyncTimeFlow(userId = userId).tryEmit(lastSyncTime)
}
override fun getVaultTimeoutInMinutes(userId: String): Int? =
@ -152,6 +168,13 @@ class FakeSettingsDiskSource : SettingsDiskSource {
//region Private helper functions
private fun getMutableLastSyncTimeFlow(
userId: String,
): MutableSharedFlow<Instant?> =
mutableLastSyncCallFlowMap.getOrPut(userId) {
bufferedMutableSharedFlow(replay = 1)
}
private fun getMutableVaultTimeoutActionsFlow(
userId: String,
): MutableSharedFlow<VaultTimeoutAction?> =