Add sends database table (#490)

This commit is contained in:
David Perez
2024-06-20 17:08:07 +01:00
committed by Álison Fernandes
parent cd707473fc
commit b6032873ec
10 changed files with 377 additions and 228 deletions
@@ -23,6 +23,11 @@ interface VaultDiskSource {
*/
fun getFolders(userId: String): Flow<List<SyncResponseJson.Folder>>
/**
* Retrieves all sends from the data source for a given [userId].
*/
fun getSends(userId: String): Flow<List<SyncResponseJson.Send>>
/**
* Replaces all [vault] data for a given [userId] with the new `vault`.
*
@@ -4,9 +4,11 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@@ -24,6 +26,7 @@ class VaultDiskSourceImpl(
private val ciphersDao: CiphersDao,
private val collectionsDao: CollectionsDao,
private val foldersDao: FoldersDao,
private val sendsDao: SendsDao,
private val json: Json,
) : VaultDiskSource {
@@ -31,6 +34,7 @@ class VaultDiskSourceImpl(
private val forceCollectionsFlow =
bufferedMutableSharedFlow<List<SyncResponseJson.Collection>>()
private val forceFolderFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
private val forceSendFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
override fun getCiphers(
userId: String,
@@ -85,6 +89,21 @@ class VaultDiskSourceImpl(
},
)
override fun getSends(
userId: String,
): Flow<List<SyncResponseJson.Send>> =
merge(
forceSendFlow,
sendsDao
.getAllSends(userId = userId)
.map { entities ->
entities.map { entity ->
json.decodeFromString<SyncResponseJson.Send>(entity.sendJson)
}
},
)
@Suppress("LongMethod")
override suspend fun replaceVaultData(
userId: String,
vault: SyncResponseJson,
@@ -132,6 +151,19 @@ class VaultDiskSourceImpl(
},
)
}
val deferredSends = async {
sendsDao.replaceAllSends(
userId = userId,
sends = vault.sends.orEmpty().map { send ->
SendEntity(
userId = userId,
id = send.id,
sendType = json.encodeToString(send.type),
sendJson = json.encodeToString(send),
)
},
)
}
// When going from 0 items to 0 items, the respective dao flow will not re-emit
// So we use this to give it a little push.
if (!deferredCiphers.await()) {
@@ -143,6 +175,9 @@ class VaultDiskSourceImpl(
if (!deferredFolders.await()) {
forceFolderFlow.tryEmit(emptyList())
}
if (!deferredSends.await()) {
forceSendFlow.tryEmit(emptyList())
}
}
}
@@ -151,10 +186,12 @@ class VaultDiskSourceImpl(
val deferredCiphers = async { ciphersDao.deleteAllCiphers(userId = userId) }
val deferredCollections = async { collectionsDao.deleteAllCollections(userId = userId) }
val deferredFolders = async { foldersDao.deleteAllFolders(userId = userId) }
val deferredSends = async { sendsDao.deleteAllSends(userId = userId) }
awaitAll(
deferredCiphers,
deferredCollections,
deferredFolders,
deferredSends,
)
}
}
@@ -0,0 +1,50 @@
package com.x8bit.bitwarden.data.vault.datasource.disk.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
import kotlinx.coroutines.flow.Flow
/**
* Provides methods for inserting, retrieving, and deleting sends from the database using the
* [SendEntity].
*/
@Dao
interface SendsDao {
/**
* Inserts multiple sends into the database.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSends(sends: List<SendEntity>)
/**
* Retrieves all sends from the database for a given [userId].
*/
@Query("SELECT * FROM sends WHERE user_id = :userId")
fun getAllSends(
userId: String,
): Flow<List<SendEntity>>
/**
* Deletes all the stored sends associated with the given [userId]. This will return the
* number of rows deleted by this query.
*/
@Query("DELETE FROM sends WHERE user_id = :userId")
suspend fun deleteAllSends(userId: String): Int
/**
* Deletes all the stored sends associated with the given [userId] and then add all new
* [sends] to the database. This will return `true` if any changes were made to the database
* and `false` otherwise.
*/
@Transaction
suspend fun replaceAllSends(userId: String, sends: List<SendEntity>): Boolean {
val deletedSendsCount = deleteAllSends(userId)
insertSends(sends)
return deletedSendsCount > 0 || sends.isNotEmpty()
}
}
@@ -7,9 +7,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTyp
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
/**
* Room database for storing any persisted data from the vault sync.
@@ -19,8 +21,9 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
CipherEntity::class,
CollectionEntity::class,
FolderEntity::class,
SendEntity::class,
],
version = 1,
version = 2,
)
@TypeConverters(ZonedDateTimeTypeConverter::class)
abstract class VaultDatabase : RoomDatabase() {
@@ -39,4 +42,9 @@ abstract class VaultDatabase : RoomDatabase() {
* Provides the DAO for accessing folder data.
*/
abstract fun folderDao(): FoldersDao
/**
* Provides the DAO for accessing send data.
*/
abstract fun sendsDao(): SendsDao
}
@@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTyp
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase
import dagger.Module
import dagger.Provides
@@ -32,6 +33,7 @@ class VaultDiskModule {
klass = VaultDatabase::class.java,
name = "vault_database",
)
.fallbackToDestructiveMigration()
.addTypeConverter(ZonedDateTimeTypeConverter())
.build()
@@ -47,17 +49,23 @@ class VaultDiskModule {
@Singleton
fun provideFolderDao(database: VaultDatabase): FoldersDao = database.folderDao()
@Provides
@Singleton
fun provideSendDao(database: VaultDatabase): SendsDao = database.sendsDao()
@Provides
@Singleton
fun provideVaultDiskSource(
ciphersDao: CiphersDao,
collectionsDao: CollectionsDao,
foldersDao: FoldersDao,
sendsDao: SendsDao,
json: Json,
): VaultDiskSource = VaultDiskSourceImpl(
ciphersDao = ciphersDao,
collectionsDao = collectionsDao,
foldersDao = foldersDao,
sendsDao = sendsDao,
json = json,
)
}
@@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.vault.datasource.disk.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* Entity representing a send in the database.
*/
@Entity(tableName = "sends")
data class SendEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "user_id", index = true)
val userId: String,
@ColumnInfo(name = "send_type")
val sendType: String,
@ColumnInfo(name = "send_json")
val sendJson: String,
)
@@ -164,6 +164,12 @@ class VaultRepositoryImpl(
observeVaultDiskCollections(activeUserId)
}
.launchIn(unconfinedScope)
// Setup sends MutableStateFlow
mutableSendDataStateFlow
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
observeVaultDiskSends(activeUserId)
}
.launchIn(unconfinedScope)
}
override fun clearUnlockedData() {
@@ -201,7 +207,6 @@ class VaultRepositoryImpl(
unlockVaultForOrganizationsIfNecessary(syncResponse = syncResponse)
storeProfileData(syncResponse = syncResponse)
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
decryptSendsAndUpdateSendDataState(sendList = syncResponse.sends)
},
onFailure = { throwable ->
mutableCiphersStateFlow.update { currentState ->
@@ -474,20 +479,6 @@ class VaultRepositoryImpl(
)
}
private suspend fun decryptSendsAndUpdateSendDataState(sendList: List<SyncResponseJson.Send>?) {
val newState = vaultSdkSource
.decryptSendList(
sendList = sendList
.orEmpty()
.toEncryptedSdkSendList(),
)
.fold(
onSuccess = { DataState.Loaded(data = SendData(sendViewList = it)) },
onFailure = { DataState.Error(error = it) },
)
mutableSendDataStateFlow.update { newState }
}
private fun observeVaultDiskCiphers(
userId: String,
): Flow<DataState<List<CipherView>>> =
@@ -535,6 +526,22 @@ class VaultRepositoryImpl(
)
}
.onEach { mutableCollectionsStateFlow.value = it }
private fun observeVaultDiskSends(
userId: String,
): Flow<DataState<SendData>> =
vaultDiskSource
.getSends(userId = userId)
.onStart { mutableSendDataStateFlow.value = DataState.Loading }
.map {
vaultSdkSource
.decryptSendList(sendList = it.toEncryptedSdkSendList())
.fold(
onSuccess = { sends -> DataState.Loaded(SendData(sends)) },
onFailure = { throwable -> DataState.Error(throwable) },
)
}
.onEach { mutableSendDataStateFlow.value = it }
}
private fun <T> Throwable.toNetworkOrErrorState(data: T?): DataState<T> =