Skip to content

Commit 8e7ec7a

Browse files
PM-15177: Improve destructive fallback logic (#4373)
1 parent 2e18458 commit 8e7ec7a

File tree

11 files changed

+102
-240
lines changed

11 files changed

+102
-240
lines changed

app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import dagger.hilt.InstallIn
3737
import dagger.hilt.android.qualifiers.ApplicationContext
3838
import dagger.hilt.components.SingletonComponent
3939
import kotlinx.serialization.json.Json
40-
import java.time.Clock
4140
import javax.inject.Singleton
4241

4342
/**
@@ -74,7 +73,6 @@ object PlatformDiskModule {
7473
fun provideEventDatabase(
7574
app: Application,
7675
databaseSchemeManager: DatabaseSchemeManager,
77-
clock: Clock,
7876
): PlatformDatabase =
7977
Room
8078
.databaseBuilder(
@@ -84,12 +82,7 @@ object PlatformDiskModule {
8482
)
8583
.fallbackToDestructiveMigration()
8684
.addTypeConverter(ZonedDateTimeTypeConverter())
87-
.addCallback(
88-
DatabaseSchemeCallback(
89-
databaseSchemeManager = databaseSchemeManager,
90-
clock = clock,
91-
),
92-
)
85+
.addCallback(DatabaseSchemeCallback(databaseSchemeManager = databaseSchemeManager))
9386
.build()
9487

9588
@Provides
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
package com.x8bit.bitwarden.data.platform.manager
22

33
import kotlinx.coroutines.flow.Flow
4-
import java.time.Instant
54

65
/**
76
* Manager for tracking changes to database scheme(s).
87
*/
98
interface DatabaseSchemeManager {
10-
119
/**
12-
* The instant of the last database schema change performed on the database, if any.
13-
*
14-
* There is only a single scheme change instant tracked for all database schemes. It is expected
15-
* that a scheme change to any database will update this value and trigger a sync.
10+
* Clears the sync state for all users and emits on the [databaseSchemeChangeFlow].
1611
*/
17-
var lastDatabaseSchemeChangeInstant: Instant?
12+
fun clearSyncState()
1813

1914
/**
20-
* A flow of the last database schema change instant.
15+
* Emits whenever the sync state hs been cleared.
2116
*/
22-
val lastDatabaseSchemeChangeInstantFlow: Flow<Instant?>
17+
val databaseSchemeChangeFlow: Flow<Unit>
2318
}
Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,27 @@
11
package com.x8bit.bitwarden.data.platform.manager
22

3+
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
34
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
4-
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
5-
import kotlinx.coroutines.CoroutineScope
6-
import kotlinx.coroutines.flow.SharingStarted
7-
import kotlinx.coroutines.flow.stateIn
8-
import java.time.Instant
5+
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
6+
import kotlinx.coroutines.flow.Flow
7+
import kotlinx.coroutines.flow.MutableSharedFlow
8+
import kotlinx.coroutines.flow.asSharedFlow
99

1010
/**
1111
* Primary implementation of [DatabaseSchemeManager].
1212
*/
1313
class DatabaseSchemeManagerImpl(
14+
val authDiskSource: AuthDiskSource,
1415
val settingsDiskSource: SettingsDiskSource,
15-
val dispatcherManager: DispatcherManager,
1616
) : DatabaseSchemeManager {
17+
private val mutableSharedFlow: MutableSharedFlow<Unit> = bufferedMutableSharedFlow()
1718

18-
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
19-
20-
override var lastDatabaseSchemeChangeInstant: Instant?
21-
get() = settingsDiskSource.lastDatabaseSchemeChangeInstant
22-
set(value) {
23-
settingsDiskSource.lastDatabaseSchemeChangeInstant = value
19+
override fun clearSyncState() {
20+
authDiskSource.userState?.accounts?.forEach { (userId, _) ->
21+
settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = null)
2422
}
23+
mutableSharedFlow.tryEmit(Unit)
24+
}
2525

26-
override val lastDatabaseSchemeChangeInstantFlow =
27-
settingsDiskSource
28-
.lastDatabaseSchemeChangeInstantFlow
29-
.stateIn(
30-
scope = unconfinedScope,
31-
started = SharingStarted.Eagerly,
32-
initialValue = settingsDiskSource.lastDatabaseSchemeChangeInstant,
33-
)
26+
override val databaseSchemeChangeFlow: Flow<Unit> = mutableSharedFlow.asSharedFlow()
3427
}

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,10 @@ object PlatformManagerModule {
304304
@Provides
305305
@Singleton
306306
fun provideDatabaseSchemeManager(
307+
authDiskSource: AuthDiskSource,
307308
settingsDiskSource: SettingsDiskSource,
308-
dispatcherManager: DispatcherManager,
309309
): DatabaseSchemeManager = DatabaseSchemeManagerImpl(
310+
authDiskSource = authDiskSource,
310311
settingsDiskSource = settingsDiskSource,
311-
dispatcherManager = dispatcherManager,
312312
)
313313
}

app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/di/GeneratorDiskModule.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import dagger.Provides
1717
import dagger.hilt.InstallIn
1818
import dagger.hilt.components.SingletonComponent
1919
import kotlinx.serialization.json.Json
20-
import java.time.Clock
2120
import javax.inject.Singleton
2221

2322
/**
@@ -51,20 +50,14 @@ object GeneratorDiskModule {
5150
fun providePasswordHistoryDatabase(
5251
app: Application,
5352
databaseSchemeManager: DatabaseSchemeManager,
54-
clock: Clock,
5553
): PasswordHistoryDatabase {
5654
return Room
5755
.databaseBuilder(
5856
context = app,
5957
klass = PasswordHistoryDatabase::class.java,
6058
name = "passcode_history_database",
6159
)
62-
.addCallback(
63-
DatabaseSchemeCallback(
64-
databaseSchemeManager = databaseSchemeManager,
65-
clock = clock,
66-
),
67-
)
60+
.addCallback(DatabaseSchemeCallback(databaseSchemeManager = databaseSchemeManager))
6861
.build()
6962
}
7063

app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallback.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.callback
33
import androidx.room.RoomDatabase
44
import androidx.sqlite.db.SupportSQLiteDatabase
55
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
6-
import java.time.Clock
76

87
/**
98
* A [RoomDatabase.Callback] for tracking database scheme changes.
109
*/
1110
class DatabaseSchemeCallback(
1211
private val databaseSchemeManager: DatabaseSchemeManager,
13-
private val clock: Clock,
1412
) : RoomDatabase.Callback() {
1513
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
16-
databaseSchemeManager.lastDatabaseSchemeChangeInstant = clock.instant()
14+
databaseSchemeManager.clearSyncState()
1715
}
1816
}

app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import dagger.Provides
1919
import dagger.hilt.InstallIn
2020
import dagger.hilt.components.SingletonComponent
2121
import kotlinx.serialization.json.Json
22-
import java.time.Clock
2322
import javax.inject.Singleton
2423

2524
/**
@@ -34,7 +33,6 @@ class VaultDiskModule {
3433
fun provideVaultDatabase(
3534
app: Application,
3635
databaseSchemeManager: DatabaseSchemeManager,
37-
clock: Clock,
3836
): VaultDatabase =
3937
Room
4038
.databaseBuilder(
@@ -43,7 +41,7 @@ class VaultDiskModule {
4341
name = "vault_database",
4442
)
4543
.fallbackToDestructiveMigration()
46-
.addCallback(DatabaseSchemeCallback(databaseSchemeManager, clock))
44+
.addCallback(DatabaseSchemeCallback(databaseSchemeManager = databaseSchemeManager))
4745
.addTypeConverter(ZonedDateTimeTypeConverter())
4846
.build()
4947

app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ import kotlinx.coroutines.flow.asSharedFlow
9999
import kotlinx.coroutines.flow.asStateFlow
100100
import kotlinx.coroutines.flow.combine
101101
import kotlinx.coroutines.flow.filter
102-
import kotlinx.coroutines.flow.filterNotNull
103102
import kotlinx.coroutines.flow.first
104103
import kotlinx.coroutines.flow.firstOrNull
105104
import kotlinx.coroutines.flow.flatMapLatest
@@ -326,9 +325,8 @@ class VaultRepositoryImpl(
326325
.launchIn(ioScope)
327326

328327
databaseSchemeManager
329-
.lastDatabaseSchemeChangeInstantFlow
330-
.filterNotNull()
331-
.onEach { sync() }
328+
.databaseSchemeChangeFlow
329+
.onEach { sync(forced = true) }
332330
.launchIn(ioScope)
333331
}
334332

@@ -361,13 +359,11 @@ class VaultRepositoryImpl(
361359
val userId = activeUserId ?: return
362360
val currentInstant = clock.instant()
363361
val lastSyncInstant = settingsDiskSource.getLastSyncTime(userId = userId)
364-
val lastDatabaseSchemeChangeInstant = databaseSchemeManager.lastDatabaseSchemeChangeInstant
365362

366363
// Sync if we have never done so, the last time was at last 30 minutes ago, or the database
367364
// scheme changed since the last sync.
368365
if (lastSyncInstant == null ||
369-
currentInstant.isAfter(lastSyncInstant.plus(30, ChronoUnit.MINUTES)) ||
370-
lastDatabaseSchemeChangeInstant?.isAfter(lastSyncInstant) == true
366+
currentInstant.isAfter(lastSyncInstant.plus(30, ChronoUnit.MINUTES))
371367
) {
372368
sync()
373369
}
@@ -1347,37 +1343,33 @@ class VaultRepositoryImpl(
13471343
val lastSyncInstant = settingsDiskSource
13481344
.getLastSyncTime(userId = userId)
13491345
?.toEpochMilli()
1350-
?: 0
1351-
val lastDatabaseSchemeChangeInstant = databaseSchemeManager
1352-
.lastDatabaseSchemeChangeInstant
1353-
?.toEpochMilli()
1354-
?: 0
1355-
syncService
1356-
.getAccountRevisionDateMillis()
1357-
.fold(
1358-
onSuccess = { serverRevisionDate ->
1359-
if (serverRevisionDate < lastSyncInstant &&
1360-
lastDatabaseSchemeChangeInstant < lastSyncInstant
1361-
) {
1362-
// We can skip the actual sync call if there is no new data or database
1363-
// scheme changes since the last sync.
1364-
vaultDiskSource.resyncVaultData(userId = userId)
1365-
settingsDiskSource.storeLastSyncTime(
1366-
userId = userId,
1367-
lastSyncTime = clock.instant(),
1368-
)
1369-
val itemsAvailable = vaultDiskSource
1370-
.getCiphers(userId)
1371-
.firstOrNull()
1372-
?.isNotEmpty() == true
1373-
return SyncVaultDataResult.Success(itemsAvailable = itemsAvailable)
1374-
}
1375-
},
1376-
onFailure = {
1377-
updateVaultStateFlowsToError(throwable = it)
1378-
return SyncVaultDataResult.Error(throwable = it)
1379-
},
1380-
)
1346+
lastSyncInstant?.let { lastSyncTimeMs ->
1347+
// If the lasSyncState is null we just sync, no checks required.
1348+
syncService
1349+
.getAccountRevisionDateMillis()
1350+
.fold(
1351+
onSuccess = { serverRevisionDate ->
1352+
if (serverRevisionDate < lastSyncTimeMs) {
1353+
// We can skip the actual sync call if there is no new data or
1354+
// database scheme changes since the last sync.
1355+
vaultDiskSource.resyncVaultData(userId = userId)
1356+
settingsDiskSource.storeLastSyncTime(
1357+
userId = userId,
1358+
lastSyncTime = clock.instant(),
1359+
)
1360+
val itemsAvailable = vaultDiskSource
1361+
.getCiphers(userId)
1362+
.firstOrNull()
1363+
?.isNotEmpty() == true
1364+
return SyncVaultDataResult.Success(itemsAvailable = itemsAvailable)
1365+
}
1366+
},
1367+
onFailure = {
1368+
updateVaultStateFlowsToError(throwable = it)
1369+
return SyncVaultDataResult.Error(throwable = it)
1370+
},
1371+
)
1372+
}
13811373
}
13821374

13831375
return syncService

0 commit comments

Comments
 (0)