Introduce OfflineCiphersDao/Entity

This commit is contained in:
Hinton 2024-10-21 15:40:08 -07:00
parent c7d7ed6cb9
commit 9cb8f414ba
No known key found for this signature in database
GPG key ID: 5F7295599C5D965C
8 changed files with 153 additions and 6 deletions

View file

@ -2,8 +2,56 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 3, "version": 3,
"identityHash": "64bb48a6bc6a544d168b0b4f4862cbcd", "identityHash": "67b7550b79460fd815162804f4a00c2e",
"entities": [ "entities": [
{
"tableName": "offline_ciphers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cipherType",
"columnName": "cipher_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cipherJson",
"columnName": "cipher_json",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_offline_ciphers_user_id",
"unique": false,
"columnNames": [
"user_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_offline_ciphers_user_id` ON `${TABLE_NAME}` (`user_id`)"
}
],
"foreignKeys": []
},
{ {
"tableName": "ciphers", "tableName": "ciphers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
@ -244,7 +292,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '64bb48a6bc6a544d168b0b4f4862cbcd')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '67b7550b79460fd815162804f4a00c2e')"
] ]
} }
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.disk package com.x8bit.bitwarden.data.vault.datasource.disk
import com.bitwarden.vault.Cipher
import com.bitwarden.vault.CipherView import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -13,7 +14,7 @@ interface VaultDiskSource {
/** /**
* Saves a cipher to the offline data source for the given [userId]. * Saves a cipher to the offline data source for the given [userId].
*/ */
suspend fun saveOfflineCipher(userId: String, cipher: CipherView) suspend fun saveOfflineCipher(userId: String, cipher: Cipher)
/** /**
* Saves a cipher to the data source for the given [userId]. * Saves a cipher to the data source for the given [userId].

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.disk package com.x8bit.bitwarden.data.vault.datasource.disk
import com.bitwarden.vault.Cipher
import com.bitwarden.vault.CipherView import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
@ -7,6 +8,7 @@ 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.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.OfflineCiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao 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.CipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
@ -31,7 +33,7 @@ import kotlinx.serialization.json.Json
*/ */
@Suppress("TooManyFunctions", "LongParameterList") @Suppress("TooManyFunctions", "LongParameterList")
class VaultDiskSourceImpl( class VaultDiskSourceImpl(
private val offlineCiphersDao: CiphersDao, private val offlineCiphersDao: OfflineCiphersDao,
private val ciphersDao: CiphersDao, private val ciphersDao: CiphersDao,
private val collectionsDao: CollectionsDao, private val collectionsDao: CollectionsDao,
private val domainsDao: DomainsDao, private val domainsDao: DomainsDao,
@ -47,7 +49,7 @@ class VaultDiskSourceImpl(
private val forceFolderFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>() private val forceFolderFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
private val forceSendFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>() private val forceSendFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
override suspend fun saveOfflineCipher(userId: String, cipher: CipherView) { override suspend fun saveOfflineCipher(userId: String, cipher: Cipher) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View file

@ -0,0 +1,57 @@
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.OfflineCipherEntity
import kotlinx.coroutines.flow.Flow
/**
* Provides methods for inserting, retrieving, and deleting ciphers from the database using the
* [OfflineCipherEntity].
*/
@Dao
interface OfflineCiphersDao {
/**
* Inserts multiple ciphers into the database.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCiphers(ciphers: List<OfflineCipherEntity>)
/**
* Retrieves all ciphers from the database for a given [userId].
*/
@Query("SELECT * FROM offline_ciphers WHERE user_id = :userId")
fun getAllCiphers(
userId: String,
): Flow<List<OfflineCipherEntity>>
/**
* Deletes all the stored ciphers associated with the given [userId]. This will return the
* number of rows deleted by this query.
*/
@Query("DELETE FROM offline_ciphers WHERE user_id = :userId")
suspend fun deleteAllCiphers(userId: String): Int
/**
* Deletes the specified cipher associated with the given [userId] and [cipherId]. This will
* return the number of rows deleted by this query.
*/
@Query("DELETE FROM offline_ciphers WHERE user_id = :userId AND id = :cipherId")
suspend fun deleteCipher(userId: String, cipherId: String): Int
/**
* Deletes all the stored ciphers associated with the given [userId] and then add all new
* [ciphers] to the database. This will return `true` if any changes were made to the database
* and `false` otherwise.
*/
@Transaction
suspend fun replaceAllCiphers(userId: String, ciphers: List<OfflineCipherEntity>): Boolean {
val deletedCiphersCount = deleteAllCiphers(userId)
insertCiphers(ciphers)
return deletedCiphersCount > 0 || ciphers.isNotEmpty()
}
}

View file

@ -8,11 +8,13 @@ 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.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.OfflineCiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao 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.CipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.OfflineCipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
/** /**
@ -20,6 +22,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
*/ */
@Database( @Database(
entities = [ entities = [
OfflineCipherEntity::class,
CipherEntity::class, CipherEntity::class,
CollectionEntity::class, CollectionEntity::class,
DomainsEntity::class, DomainsEntity::class,
@ -32,6 +35,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
@TypeConverters(ZonedDateTimeTypeConverter::class) @TypeConverters(ZonedDateTimeTypeConverter::class)
abstract class VaultDatabase : RoomDatabase() { abstract class VaultDatabase : RoomDatabase() {
/**
* Provides the DAO for accessing cipher data.
*/
abstract fun offlineCipherDao(): OfflineCiphersDao
/** /**
* Provides the DAO for accessing cipher data. * Provides the DAO for accessing cipher data.
*/ */

View file

@ -10,6 +10,7 @@ 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.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.OfflineCiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase import com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase
import dagger.Module import dagger.Module
@ -39,6 +40,10 @@ class VaultDiskModule {
.addTypeConverter(ZonedDateTimeTypeConverter()) .addTypeConverter(ZonedDateTimeTypeConverter())
.build() .build()
@Provides
@Singleton
fun provideOfflineCipherDao(database: VaultDatabase): OfflineCiphersDao = database.offlineCipherDao()
@Provides @Provides
@Singleton @Singleton
fun provideCipherDao(database: VaultDatabase): CiphersDao = database.cipherDao() fun provideCipherDao(database: VaultDatabase): CiphersDao = database.cipherDao()
@ -62,6 +67,7 @@ class VaultDiskModule {
@Provides @Provides
@Singleton @Singleton
fun provideVaultDiskSource( fun provideVaultDiskSource(
offlineCiphersDao: OfflineCiphersDao,
ciphersDao: CiphersDao, ciphersDao: CiphersDao,
collectionsDao: CollectionsDao, collectionsDao: CollectionsDao,
domainsDao: DomainsDao, domainsDao: DomainsDao,
@ -70,6 +76,7 @@ class VaultDiskModule {
json: Json, json: Json,
dispatcherManager: DispatcherManager, dispatcherManager: DispatcherManager,
): VaultDiskSource = VaultDiskSourceImpl( ): VaultDiskSource = VaultDiskSourceImpl(
offlineCiphersDao = offlineCiphersDao,
ciphersDao = ciphersDao, ciphersDao = ciphersDao,
collectionsDao = collectionsDao, collectionsDao = collectionsDao,
domainsDao = domainsDao, domainsDao = domainsDao,

View file

@ -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 cipher in the database.
*/
@Entity(tableName = "offline_ciphers")
data class OfflineCipherEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "user_id", index = true)
val userId: String,
@ColumnInfo(name = "cipher_type")
val cipherType: String,
@ColumnInfo(name = "cipher_json")
val cipherJson: String,
)

View file

@ -53,7 +53,7 @@ class CipherManagerImpl(
userId = userId, userId = userId,
cipherView = cipherView cipherView = cipherView
) )
.flatMap { vaultDiskSource.saveOfflineCipher(userId = userId, cipher = it) } .map { vaultDiskSource.saveOfflineCipher(userId = userId, cipher = it) }
.fold( .fold(
onFailure = { CreateCipherResult.Error }, onFailure = { CreateCipherResult.Error },
onSuccess = { CreateCipherResult.Success } onSuccess = { CreateCipherResult.Success }