PM-29795: Move FileManager to data module (#6268)

This commit is contained in:
David Perez
2025-12-15 18:19:32 +00:00
committed by GitHub
parent b4414073c7
commit bdbcd5bdc2
22 changed files with 47 additions and 40 deletions
@@ -12,6 +12,7 @@ import com.bitwarden.core.data.manager.toast.ToastManagerImpl
import com.bitwarden.cxf.registry.CredentialExchangeRegistry
import com.bitwarden.cxf.registry.dsl.credentialExchangeRegistry
import com.bitwarden.data.manager.NativeLibraryManager
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.repository.ServerConfigRepository
import com.bitwarden.network.BitwardenServiceClient
import com.bitwarden.network.service.EventService
@@ -84,7 +85,6 @@ import com.x8bit.bitwarden.data.platform.repository.DebugMenuRepository
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import dagger.Module
@@ -5,9 +5,9 @@ import android.util.Log
import com.bitwarden.annotation.OmitFromCoverage
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
import com.bitwarden.core.data.util.toFormattedPattern
import com.bitwarden.data.manager.file.FileManager
import com.x8bit.bitwarden.BuildConfig
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
import com.x8bit.bitwarden.data.vault.manager.FileManager
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.BufferedWriter
@@ -1,48 +0,0 @@
package com.x8bit.bitwarden.data.platform.util
import android.os.Build
import com.bitwarden.annotation.OmitFromCoverage
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
private const val DEFAULT_BUFFER_SIZE = 8192
/**
* Reads all bytes from this input stream and writes the bytes to the
* given output stream in the order that they are read. On return, this
* input stream will be at end of stream. This method does not close either
* stream.
*
* This method may block indefinitely reading from the input stream, or
* writing to the output stream. The behavior for the case where the input
* and/or output stream is <i>asynchronously closed</i>, or the thread
* interrupted during the transfer, is highly input and output stream
* specific, and therefore not specified.
*
* If an I/O error occurs reading from the input stream or writing to the
* output stream, then it may do so after some bytes have been read or
* written. Consequently the input stream may not be at end of stream and
* one, or both, streams may be in an inconsistent state. It is strongly
* recommended that both streams be promptly closed if an I/O error occurs.
*
* @param outputStream the output stream, non-null
* @return the number of bytes transferred
* @throws IOException if an I/O error occurs when reading or writing
* @throws NullPointerException if {@code out} is {@code null}
*/
@OmitFromCoverage
fun InputStream.sdkAgnosticTransferTo(outputStream: OutputStream): Long {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
transferTo(outputStream)
} else {
var transferred: Long = 0
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (this.read(buffer, 0, DEFAULT_BUFFER_SIZE).also { read = it } >= 0) {
outputStream.write(buffer, 0, read)
transferred += read.toLong()
}
transferred
}
}
@@ -6,6 +6,8 @@ import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.core.data.util.flatMap
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.manager.model.DownloadResult
import com.bitwarden.network.model.AttachmentJsonResponse
import com.bitwarden.network.model.CreateCipherInOrganizationJsonRequest
import com.bitwarden.network.model.CreateCipherResponseJson
@@ -25,7 +27,6 @@ import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.manager.model.DownloadResult
import com.x8bit.bitwarden.data.vault.manager.model.GetCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
@@ -1,64 +0,0 @@
package com.x8bit.bitwarden.data.vault.manager
import android.net.Uri
import com.bitwarden.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.vault.manager.model.DownloadResult
import com.x8bit.bitwarden.data.vault.manager.model.ZipFileResult
import java.io.File
/**
* Manages reading files.
*/
@OmitFromCoverage
interface FileManager {
/**
* Absolute path to the private file storage directory.
*/
val filesDirectory: String
/**
* Absolute path to the private logs storage directory.
*/
val logsDirectory: String
/**
* Deletes [files] from disk.
*/
suspend fun delete(vararg files: File)
/**
* Downloads a file temporarily to cache from [url]. A successful [DownloadResult] will contain
* the final file path.
*/
suspend fun downloadFileToCache(url: String): DownloadResult
/**
* Writes an existing [file] to a [fileUri]. `true` will be returned if the file was
* successfully saved.
*/
suspend fun fileToUri(fileUri: Uri, file: File): Boolean
/**
* Writes an [dataString] to a [fileUri]. `true` will be returned if the file was
* successfully saved.
*/
suspend fun stringToUri(fileUri: Uri, dataString: String): Boolean
/**
* Reads the [fileUri] into memory. A successful result will contain the raw [ByteArray].
*/
suspend fun uriToByteArray(fileUri: Uri): Result<ByteArray>
/**
* Reads the file or folder on disk from the [uri] and creates a temporary zip file. A
* successful result will contain the zip [File] reference.
*/
suspend fun zipUriToCache(uri: Uri): ZipFileResult
/**
* Reads the [fileUri] into a file on disk. A successful result will contain the [File]
* reference.
*/
suspend fun writeUriToCache(fileUri: Uri): Result<File>
}
@@ -1,233 +0,0 @@
@file:OmitFromCoverage
package com.x8bit.bitwarden.data.vault.manager
import android.content.Context
import android.net.Uri
import com.bitwarden.annotation.OmitFromCoverage
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
import com.bitwarden.network.service.DownloadService
import com.x8bit.bitwarden.data.platform.util.sdkAgnosticTransferTo
import com.x8bit.bitwarden.data.vault.manager.model.DownloadResult
import com.x8bit.bitwarden.data.vault.manager.model.ZipFileResult
import kotlinx.coroutines.withContext
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.UUID
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
/**
* The buffer size to be used when reading from an input stream.
*/
private const val BUFFER_SIZE: Int = 1024
/**
* The default implementation of the [FileManager] interface.
*/
class FileManagerImpl(
private val context: Context,
private val downloadService: DownloadService,
private val dispatcherManager: DispatcherManager,
) : FileManager {
override val filesDirectory: String
get() = context.filesDir.absolutePath
override val logsDirectory: String
get() = "${context.dataDir}/logs"
override suspend fun delete(vararg files: File) {
withContext(dispatcherManager.io) {
files.forEach { it.delete() }
}
}
@Suppress("NestedBlockDepth")
override suspend fun downloadFileToCache(url: String): DownloadResult {
val response = downloadService
.getDataStream(url)
.fold(
onSuccess = { it },
onFailure = { return DownloadResult.Failure(error = it) },
)
// Create a temporary file in cache to write to
val file = File(context.cacheDir, UUID.randomUUID().toString())
withContext(dispatcherManager.io) {
val stream = response.byteStream()
stream.use {
val buffer = ByteArray(BUFFER_SIZE)
var progress = 0
FileOutputStream(file).use { fos ->
@Suppress("TooGenericExceptionCaught")
try {
var read = stream.read(buffer)
while (read > 0) {
fos.write(buffer, 0, read)
progress += read
read = stream.read(buffer)
}
fos.flush()
} catch (e: RuntimeException) {
return@withContext DownloadResult.Failure(error = e)
}
}
}
}
return DownloadResult.Success(file)
}
@Suppress("NestedBlockDepth")
override suspend fun fileToUri(fileUri: Uri, file: File): Boolean {
@Suppress("TooGenericExceptionCaught")
return try {
withContext(dispatcherManager.io) {
context
.contentResolver
.openOutputStream(fileUri)
?.use { outputStream ->
FileInputStream(file).use { inputStream ->
val buffer = ByteArray(BUFFER_SIZE)
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
outputStream.write(buffer, 0, length)
}
}
}
}
true
} catch (_: RuntimeException) {
false
}
}
override suspend fun stringToUri(fileUri: Uri, dataString: String): Boolean {
@Suppress("TooGenericExceptionCaught")
return try {
withContext(dispatcherManager.io) {
context
.contentResolver
.openOutputStream(fileUri)
?.use { outputStream ->
outputStream.write(dataString.toByteArray())
}
}
true
} catch (_: RuntimeException) {
false
}
}
override suspend fun uriToByteArray(fileUri: Uri): Result<ByteArray> =
runCatching {
withContext(dispatcherManager.io) {
context
.contentResolver
.openInputStream(fileUri)
?.use { inputStream ->
ByteArrayOutputStream().use { outputStream ->
val buffer = ByteArray(BUFFER_SIZE)
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
outputStream.write(buffer, 0, length)
}
outputStream.toByteArray()
}
}
?: throw IllegalStateException("Stream has crashed")
}
}
override suspend fun zipUriToCache(
uri: Uri,
): ZipFileResult =
runCatching {
withContext(dispatcherManager.io) {
val sourceFile = File(uri.toString())
if (!sourceFile.exists()) {
ZipFileResult.NothingToZip
} else {
val zipFile = File.createTempFile(
"bitwarden_flight_recorder",
".zip",
context.cacheDir,
)
FileOutputStream(zipFile).use { fos ->
BufferedOutputStream(fos).use { bos ->
ZipOutputStream(bos).use { zos ->
zos.zipFiles(sourceFile = sourceFile)
}
}
}
ZipFileResult.Success(file = zipFile)
}
}
}
.fold(
onFailure = { ZipFileResult.Failure(error = it) },
onSuccess = { it },
)
override suspend fun writeUriToCache(fileUri: Uri): Result<File> =
runCatching {
withContext(dispatcherManager.io) {
val tempFileName = "temp_send_file.bw"
context
.contentResolver
.openInputStream(fileUri)
?.use { inputStream ->
context.openFileOutput(tempFileName, Context.MODE_PRIVATE)
.use { outputStream ->
inputStream.sdkAgnosticTransferTo(outputStream)
}
}
?: throw IllegalStateException("Stream has crashed")
File(context.filesDir, tempFileName)
}
}
}
/**
* A helper function to write the files to a zip file.
*/
@Suppress("NestedBlockDepth")
private fun ZipOutputStream.zipFiles(
sourceFile: File,
parentDir: String? = null,
) {
if (sourceFile.isDirectory) {
val parentDirName = sourceFile.name + File.separator
val entry = ZipEntry(parentDirName).apply {
this.time = sourceFile.lastModified()
this.size = sourceFile.length()
}
this.putNextEntry(entry)
sourceFile.listFiles().orEmpty().forEach { file ->
this.zipFiles(sourceFile = file, parentDir = parentDirName)
}
} else {
FileInputStream(sourceFile).use { fis ->
BufferedInputStream(fis).use { bis ->
val entry = ZipEntry("${parentDir.orEmpty()}/${sourceFile.name}").apply {
this.time = sourceFile.lastModified()
this.size = sourceFile.length()
}
this.putNextEntry(entry)
val data = ByteArray(BUFFER_SIZE)
while (true) {
val readBytes = bis.read(data)
if (readBytes == -1) break
this.write(data, 0, readBytes)
}
}
}
}
}
@@ -5,6 +5,7 @@ import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.core.data.util.flatMap
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.network.model.CreateFileSendResponse
import com.bitwarden.network.model.CreateSendJsonResponse
import com.bitwarden.network.model.UpdateSendResponseJson
@@ -3,8 +3,8 @@ package com.x8bit.bitwarden.data.vault.manager.di
import android.content.Context
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
import com.bitwarden.core.data.manager.realtime.RealtimeManager
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.network.service.CiphersService
import com.bitwarden.network.service.DownloadService
import com.bitwarden.network.service.FolderService
import com.bitwarden.network.service.SendsService
import com.bitwarden.network.service.SyncService
@@ -27,8 +27,6 @@ import com.x8bit.bitwarden.data.vault.manager.CipherManager
import com.x8bit.bitwarden.data.vault.manager.CipherManagerImpl
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManager
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManagerImpl
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.manager.FileManagerImpl
import com.x8bit.bitwarden.data.vault.manager.FolderManager
import com.x8bit.bitwarden.data.vault.manager.FolderManagerImpl
import com.x8bit.bitwarden.data.vault.manager.PinProtectedUserKeyManager
@@ -127,18 +125,6 @@ object VaultManagerModule {
dispatcherManager = dispatcherManager,
)
@Provides
@Singleton
fun provideFileManager(
@ApplicationContext context: Context,
downloadService: DownloadService,
dispatcherManager: DispatcherManager,
): FileManager = FileManagerImpl(
context = context,
downloadService = downloadService,
dispatcherManager = dispatcherManager,
)
@Provides
@Singleton
fun provideVaultLockManager(
@@ -1,20 +0,0 @@
package com.x8bit.bitwarden.data.vault.manager.model
import java.io.File
/**
* Represents a result from downloading a raw file.
*/
sealed class DownloadResult {
/**
* The download was a success, and was saved to [file].
*/
data class Success(val file: File) : DownloadResult()
/**
* The download failed.
*/
data class Failure(
val error: Throwable,
) : DownloadResult()
}
@@ -1,23 +0,0 @@
package com.x8bit.bitwarden.data.vault.manager.model
import java.io.File
/**
* Represents a result from zipping a raw file or folder.
*/
sealed class ZipFileResult {
/**
* The zip was a success, and was saved to the temporary [file].
*/
data class Success(val file: File) : ZipFileResult()
/**
* There was no file or folder to zip.
*/
data object NothingToZip : ZipFileResult()
/**
* The zip failed in some generic manner.
*/
data class Failure(val error: Throwable) : ZipFileResult()
}
@@ -6,6 +6,7 @@ import androidx.core.net.toUri
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.repository.model.Environment
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.base.util.isValidUri
@@ -21,7 +22,6 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost
import com.x8bit.bitwarden.data.platform.manager.CertificateManager
import com.x8bit.bitwarden.data.platform.manager.model.ImportPrivateKeyResult
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.ui.platform.manager.keychain.model.PrivateKeyAliasSelectionResult
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -5,6 +5,7 @@ import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.core.data.util.toFormattedPattern
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.network.model.PolicyTypeJson
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
@@ -22,7 +23,6 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.platform.manager.util.hasRestrictItemTypes
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.PasswordStrengthState
@@ -4,14 +4,14 @@ import android.os.Parcelable
import androidx.core.net.toUri
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.manager.model.ZipFileResult
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.manager.model.ZipFileResult
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.util.toViewState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
@@ -8,6 +8,7 @@ import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.repository.util.combineDataStates
import com.bitwarden.core.data.repository.util.mapNullable
import com.bitwarden.core.util.persistentListOfNotNull
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.repository.util.baseIconUrl
import com.bitwarden.ui.platform.base.BackgroundEvent
import com.bitwarden.ui.platform.base.BaseViewModel
@@ -29,7 +30,6 @@ import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.DownloadAttachmentResult
@@ -6,6 +6,8 @@ import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.manager.model.DownloadResult
import com.bitwarden.network.model.AttachmentJsonRequest
import com.bitwarden.network.model.CreateCipherInOrganizationJsonRequest
import com.bitwarden.network.model.CreateCipherResponseJson
@@ -42,7 +44,6 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockEncryptionContext
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkAttachment
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipher
import com.x8bit.bitwarden.data.vault.manager.model.DownloadResult
import com.x8bit.bitwarden.data.vault.manager.model.GetCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
@@ -6,6 +6,7 @@ import com.bitwarden.core.data.repository.error.MissingPropertyException
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.network.model.CreateFileSendResponse
import com.bitwarden.network.model.CreateSendJsonResponse
import com.bitwarden.network.model.SendTypeJson
@@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.repository.model.Environment
import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
@@ -16,7 +17,6 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost
import com.x8bit.bitwarden.data.platform.manager.CertificateManager
import com.x8bit.bitwarden.data.platform.manager.model.ImportPrivateKeyResult
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.ui.platform.manager.keychain.model.PrivateKeyAliasSelectionResult
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
import io.mockk.coEvery
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.exportvault
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.repository.model.Environment
import com.bitwarden.exporters.ExportFormat
import com.bitwarden.network.model.PolicyTypeJson
@@ -23,7 +24,6 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.PasswordStrengthState
@@ -3,13 +3,13 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recorded
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.manager.model.ZipFileResult
import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.manager.model.ZipFileResult
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsAction
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsEvent
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsState
@@ -6,6 +6,7 @@ import app.cash.turbine.test
import com.bitwarden.collections.CollectionView
import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.data.manager.file.FileManager
import com.bitwarden.data.repository.model.Environment
import com.bitwarden.data.repository.util.baseIconUrl
import com.bitwarden.network.model.OrganizationType
@@ -38,7 +39,6 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipherPermissions
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult