From 9cb8f414bafda9e49c37ac9ff8cf4a04e4fe8ac5 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 21 Oct 2024 15:40:08 -0700 Subject: [PATCH] Introduce OfflineCiphersDao/Entity --- .../3.json | 52 ++++++++++++++++- .../vault/datasource/disk/VaultDiskSource.kt | 3 +- .../datasource/disk/VaultDiskSourceImpl.kt | 6 +- .../datasource/disk/dao/OfflineCiphersDao.kt | 57 +++++++++++++++++++ .../datasource/disk/database/VaultDatabase.kt | 8 +++ .../datasource/disk/di/VaultDiskModule.kt | 7 +++ .../disk/entity/OfflineCipherEntity.kt | 24 ++++++++ .../data/vault/manager/CipherManagerImpl.kt | 2 +- 8 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/OfflineCiphersDao.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/OfflineCipherEntity.kt diff --git a/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/3.json b/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/3.json index 073f0546f..bed661439 100644 --- a/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/3.json +++ b/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/3.json @@ -2,8 +2,56 @@ "formatVersion": 1, "database": { "version": 3, - "identityHash": "64bb48a6bc6a544d168b0b4f4862cbcd", + "identityHash": "67b7550b79460fd815162804f4a00c2e", "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", "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": [], "setupQueries": [ "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')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt index 924ff8c70..83b7c9d45 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.data.vault.datasource.disk +import com.bitwarden.vault.Cipher import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import kotlinx.coroutines.flow.Flow @@ -13,7 +14,7 @@ interface VaultDiskSource { /** * 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]. diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt index 0360f9f3d..b4850666b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.data.vault.datasource.disk +import com.bitwarden.vault.Cipher import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager 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.DomainsDao 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.entity.CipherEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity @@ -31,7 +33,7 @@ import kotlinx.serialization.json.Json */ @Suppress("TooManyFunctions", "LongParameterList") class VaultDiskSourceImpl( - private val offlineCiphersDao: CiphersDao, + private val offlineCiphersDao: OfflineCiphersDao, private val ciphersDao: CiphersDao, private val collectionsDao: CollectionsDao, private val domainsDao: DomainsDao, @@ -47,7 +49,7 @@ class VaultDiskSourceImpl( private val forceFolderFlow = bufferedMutableSharedFlow>() private val forceSendFlow = bufferedMutableSharedFlow>() - override suspend fun saveOfflineCipher(userId: String, cipher: CipherView) { + override suspend fun saveOfflineCipher(userId: String, cipher: Cipher) { TODO("Not yet implemented") } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/OfflineCiphersDao.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/OfflineCiphersDao.kt new file mode 100644 index 000000000..06b9c4225 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/OfflineCiphersDao.kt @@ -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) + + /** + * Retrieves all ciphers from the database for a given [userId]. + */ + @Query("SELECT * FROM offline_ciphers WHERE user_id = :userId") + fun getAllCiphers( + userId: String, + ): Flow> + + /** + * 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): Boolean { + val deletedCiphersCount = deleteAllCiphers(userId) + insertCiphers(ciphers) + return deletedCiphersCount > 0 || ciphers.isNotEmpty() + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt index 495e038d3..5e1bfd63c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt @@ -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.DomainsDao 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.entity.CipherEntity 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.FolderEntity +import com.x8bit.bitwarden.data.vault.datasource.disk.entity.OfflineCipherEntity 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( entities = [ + OfflineCipherEntity::class, CipherEntity::class, CollectionEntity::class, DomainsEntity::class, @@ -32,6 +35,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity @TypeConverters(ZonedDateTimeTypeConverter::class) abstract class VaultDatabase : RoomDatabase() { + /** + * Provides the DAO for accessing cipher data. + */ + abstract fun offlineCipherDao(): OfflineCiphersDao + /** * Provides the DAO for accessing cipher data. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt index 2de9de16f..70f182c5b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt @@ -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.DomainsDao 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.database.VaultDatabase import dagger.Module @@ -39,6 +40,10 @@ class VaultDiskModule { .addTypeConverter(ZonedDateTimeTypeConverter()) .build() + @Provides + @Singleton + fun provideOfflineCipherDao(database: VaultDatabase): OfflineCiphersDao = database.offlineCipherDao() + @Provides @Singleton fun provideCipherDao(database: VaultDatabase): CiphersDao = database.cipherDao() @@ -62,6 +67,7 @@ class VaultDiskModule { @Provides @Singleton fun provideVaultDiskSource( + offlineCiphersDao: OfflineCiphersDao, ciphersDao: CiphersDao, collectionsDao: CollectionsDao, domainsDao: DomainsDao, @@ -70,6 +76,7 @@ class VaultDiskModule { json: Json, dispatcherManager: DispatcherManager, ): VaultDiskSource = VaultDiskSourceImpl( + offlineCiphersDao = offlineCiphersDao, ciphersDao = ciphersDao, collectionsDao = collectionsDao, domainsDao = domainsDao, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/OfflineCipherEntity.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/OfflineCipherEntity.kt new file mode 100644 index 000000000..73f726145 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/OfflineCipherEntity.kt @@ -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, +) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt index 702667c93..f4f53b9ab 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt @@ -53,7 +53,7 @@ class CipherManagerImpl( userId = userId, cipherView = cipherView ) - .flatMap { vaultDiskSource.saveOfflineCipher(userId = userId, cipher = it) } + .map { vaultDiskSource.saveOfflineCipher(userId = userId, cipher = it) } .fold( onFailure = { CreateCipherResult.Error }, onSuccess = { CreateCipherResult.Success }