mirror of
https://github.com/bitwarden/android.git
synced 2026-08-15 08:34:16 -05:00
PM-41429: Chore: Decouple dependencies (#7238)
This commit is contained in:
@@ -3,7 +3,9 @@ package com.bitwarden.data.manager.di
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.bitwarden.core.data.manager.BuildInfoManager
|
||||
import com.bitwarden.core.data.manager.UuidManager
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.data.datasource.disk.ConfigDiskSource
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.manager.BitwardenPackageManager
|
||||
import com.bitwarden.data.manager.BitwardenPackageManagerImpl
|
||||
@@ -17,8 +19,6 @@ import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManagerImpl
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderWriter
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderWriterImpl
|
||||
import com.bitwarden.data.repository.ServerConfigRepository
|
||||
import com.bitwarden.network.service.DownloadService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -50,11 +50,11 @@ object DataManagerModule {
|
||||
@Singleton
|
||||
fun provideFileManager(
|
||||
@ApplicationContext context: Context,
|
||||
downloadService: DownloadService,
|
||||
uuidManager: UuidManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): FileManager = FileManagerImpl(
|
||||
context = context,
|
||||
downloadService = downloadService,
|
||||
uuidManager = uuidManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
||||
@@ -81,13 +81,13 @@ object DataManagerModule {
|
||||
fileManager: FileManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
buildInfoManager: BuildInfoManager,
|
||||
serverConfigRepository: ServerConfigRepository,
|
||||
configDiskSource: ConfigDiskSource,
|
||||
): FlightRecorderWriter = FlightRecorderWriterImpl(
|
||||
clock = clock,
|
||||
fileManager = fileManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
buildInfoManager = buildInfoManager,
|
||||
serverConfigRepository = serverConfigRepository,
|
||||
configDiskSource = configDiskSource,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -2,9 +2,9 @@ package com.bitwarden.data.manager.file
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitwarden.annotation.OmitFromCoverage
|
||||
import com.bitwarden.data.manager.model.DownloadResult
|
||||
import com.bitwarden.data.manager.model.ZipFileResult
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Manages reading files.
|
||||
@@ -28,10 +28,10 @@ interface FileManager {
|
||||
suspend fun delete(vararg files: File)
|
||||
|
||||
/**
|
||||
* Downloads a file temporarily to cache from [url]. A successful [DownloadResult] will contain
|
||||
* Opens the provided [stream] and writes it to cache. A successful [Result] will contain
|
||||
* the final file path.
|
||||
*/
|
||||
suspend fun downloadFileToCache(url: String): DownloadResult
|
||||
suspend fun streamFileToCache(stream: InputStream): Result<File>
|
||||
|
||||
/**
|
||||
* Writes an existing [file] to a [fileUri]. `true` will be returned if the file was
|
||||
|
||||
@@ -5,11 +5,12 @@ package com.bitwarden.data.manager.file
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.bitwarden.annotation.OmitFromCoverage
|
||||
import com.bitwarden.core.data.manager.UuidManager
|
||||
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.sdkAgnosticTransferTo
|
||||
import com.bitwarden.data.manager.model.DownloadResult
|
||||
import com.bitwarden.data.manager.model.ZipFileResult
|
||||
import com.bitwarden.network.service.DownloadService
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
@@ -18,7 +19,7 @@ import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.UUID
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@@ -32,7 +33,7 @@ private const val BUFFER_SIZE: Int = 1024
|
||||
*/
|
||||
internal class FileManagerImpl(
|
||||
private val context: Context,
|
||||
private val downloadService: DownloadService,
|
||||
private val uuidManager: UuidManager,
|
||||
private val dispatcherManager: DispatcherManager,
|
||||
) : FileManager {
|
||||
|
||||
@@ -48,20 +49,10 @@ internal class FileManagerImpl(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NestedBlockDepth")
|
||||
override suspend fun downloadFileToCache(url: String): DownloadResult {
|
||||
val response = downloadService
|
||||
.getDataStream(url)
|
||||
.fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { return DownloadResult.Failure(error = it) },
|
||||
)
|
||||
|
||||
override suspend fun streamFileToCache(stream: InputStream): Result<File> {
|
||||
// Create a temporary file in cache to write to
|
||||
val file = File(context.cacheDir, UUID.randomUUID().toString())
|
||||
|
||||
withContext(dispatcherManager.io) {
|
||||
val stream = response.byteStream()
|
||||
val file = File(context.cacheDir, uuidManager.generateUuid())
|
||||
return withContext(dispatcherManager.io) {
|
||||
stream.use {
|
||||
val buffer = ByteArray(BUFFER_SIZE)
|
||||
var progress = 0
|
||||
@@ -76,13 +67,12 @@ internal class FileManagerImpl(
|
||||
}
|
||||
fos.flush()
|
||||
} catch (e: RuntimeException) {
|
||||
return@withContext DownloadResult.Failure(error = e)
|
||||
return@withContext e.asFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
file.asSuccess()
|
||||
}
|
||||
|
||||
return DownloadResult.Success(file)
|
||||
}
|
||||
|
||||
@Suppress("NestedBlockDepth")
|
||||
|
||||
+8
-5
@@ -8,9 +8,9 @@ import com.bitwarden.core.data.manager.BuildInfoManager
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.core.data.manager.util.deviceData
|
||||
import com.bitwarden.core.data.util.toFormattedPattern
|
||||
import com.bitwarden.data.datasource.disk.ConfigDiskSource
|
||||
import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.bitwarden.data.manager.file.FileManager
|
||||
import com.bitwarden.data.repository.ServerConfigRepository
|
||||
import com.bitwarden.network.util.redactHostnamesInMessage
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
@@ -34,12 +34,15 @@ internal class FlightRecorderWriterImpl(
|
||||
private val fileManager: FileManager,
|
||||
private val dispatcherManager: DispatcherManager,
|
||||
private val buildInfoManager: BuildInfoManager,
|
||||
private val serverConfigRepository: ServerConfigRepository,
|
||||
private val configDiskSource: ConfigDiskSource,
|
||||
) : FlightRecorderWriter {
|
||||
private val configuredHosts: Set<String>
|
||||
get() {
|
||||
val environment = serverConfigRepository.serverConfigStateFlow.value
|
||||
?.serverData?.environment ?: return emptySet()
|
||||
val environment = configDiskSource
|
||||
.serverConfig
|
||||
?.serverData
|
||||
?.environment
|
||||
?: return emptySet()
|
||||
return listOfNotNull(
|
||||
environment.vaultUrl,
|
||||
environment.apiUrl,
|
||||
@@ -75,7 +78,7 @@ internal class FlightRecorderWriterImpl(
|
||||
logFile.createNewFile()
|
||||
|
||||
val ciInfo = buildInfoManager.ciBuildInfo?.takeIf { it.isNotBlank() }
|
||||
val serverData = serverConfigRepository.serverConfigStateFlow.value?.serverData
|
||||
val serverData = configDiskSource.serverConfig?.serverData
|
||||
val serverInfo = StringBuilder()
|
||||
.append(serverData?.server?.name ?: "Bitwarden Cloud")
|
||||
.apply {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.bitwarden.data.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()
|
||||
}
|
||||
@@ -3,23 +3,27 @@ package com.bitwarden.data.manager.file
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.bitwarden.core.data.manager.UuidManager
|
||||
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.network.service.DownloadService
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertInstanceOf
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
|
||||
/**
|
||||
* Test class for [FileManagerImpl].
|
||||
@@ -28,18 +32,25 @@ class FileManagerTest {
|
||||
|
||||
private val fakeDispatcherManager = FakeDispatcherManager()
|
||||
private val mockContentResolver = mockk<ContentResolver>()
|
||||
private val downloadService = mockk<DownloadService>()
|
||||
private val cacheDirectory: File = Files.createTempDirectory("cache").toFile()
|
||||
private val mockContext = mockk<Context> {
|
||||
every { contentResolver } returns mockContentResolver
|
||||
every { cacheDir } returns cacheDirectory
|
||||
}
|
||||
private val uuidManager: UuidManager = mockk()
|
||||
private val mockUri = mockk<Uri>()
|
||||
|
||||
private val fileManager = FileManagerImpl(
|
||||
context = mockContext,
|
||||
uuidManager = uuidManager,
|
||||
dispatcherManager = fakeDispatcherManager,
|
||||
downloadService = downloadService,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
cacheDirectory.deleteRecursively()
|
||||
}
|
||||
|
||||
//region stringToUri Tests
|
||||
|
||||
@Test
|
||||
@@ -258,6 +269,62 @@ class FileManagerTest {
|
||||
|
||||
//endregion
|
||||
|
||||
//region streamFileToCache Tests
|
||||
|
||||
@Test
|
||||
fun `streamFileToCache with valid stream should return Success with the cached file`() =
|
||||
runTest {
|
||||
val testData = "Test content".toByteArray()
|
||||
val mockInputStream = createMockInputStream(testData)
|
||||
every { uuidManager.generateUuid() } returns "mockUuid"
|
||||
|
||||
val result = fileManager.streamFileToCache(stream = mockInputStream)
|
||||
|
||||
val file = result.getOrThrow()
|
||||
assertEquals(File(cacheDirectory, "mockUuid"), file)
|
||||
assertArrayEquals(testData, file.readBytes())
|
||||
verify(exactly = 1) { mockInputStream.close() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `streamFileToCache with empty stream should return Success with an empty file`() = runTest {
|
||||
val mockInputStream = createMockInputStream(testData = ByteArray(0))
|
||||
every { uuidManager.generateUuid() } returns "mockUuid"
|
||||
|
||||
val result = fileManager.streamFileToCache(stream = mockInputStream)
|
||||
|
||||
val file = result.getOrThrow()
|
||||
assertEquals(0, file.length())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `streamFileToCache with large stream should write the stream completely`() = runTest {
|
||||
val testData = "L".repeat(5000).toByteArray()
|
||||
val mockInputStream = createMockInputStream(testData)
|
||||
every { uuidManager.generateUuid() } returns "mockUuid"
|
||||
|
||||
val result = fileManager.streamFileToCache(stream = mockInputStream)
|
||||
|
||||
assertArrayEquals(testData, result.getOrThrow().readBytes())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `streamFileToCache with read failure should return Failure`() = runTest {
|
||||
val error = RuntimeException("Read failed")
|
||||
val mockInputStream = mockk<InputStream> {
|
||||
every { read(any<ByteArray>()) } throws error
|
||||
every { close() } just runs
|
||||
}
|
||||
every { uuidManager.generateUuid() } returns "mockUuid"
|
||||
|
||||
val result = fileManager.streamFileToCache(stream = mockInputStream)
|
||||
|
||||
assertEquals(error, result.exceptionOrNull())
|
||||
verify(exactly = 1) { mockInputStream.close() }
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region Helper Methods
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user