2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-15 18:12:54 +00:00

[fenix] Add updated downloader and file manager

This commit is contained in:
MatthewTighe 2022-08-17 12:19:39 -07:00 committed by mergify[bot]
parent af25894e05
commit 0b1667df4b
10 changed files with 526 additions and 108 deletions

View File

@ -0,0 +1,94 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.wallpapers
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.concept.fetch.Client
import mozilla.components.concept.fetch.Request
import mozilla.components.concept.fetch.isSuccess
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.BuildConfig
import java.io.File
/**
* Can download wallpapers from a remote host.
*
* @param context Required for writing files to local storage.
* @param client Required for fetching files from network.
*/
class LegacyWallpaperDownloader(
private val context: Context,
private val client: Client,
) {
private val logger = Logger("WallpaperDownloader")
private val remoteHost = BuildConfig.WALLPAPER_URL
/**
* Downloads a wallpaper from the network. Will try to fetch 4 versions of each wallpaper:
* portrait/light - portrait/dark - landscape/light - landscape/dark. These are expected to be
* found at a remote path in the form:
* <WALLPAPER_URL>/<resolution>/<orientation>/<app theme>/<wallpaper theme>/<wallpaper name>.png
*/
suspend fun downloadWallpaper(wallpaper: Wallpaper) = withContext(Dispatchers.IO) {
if (remoteHost.isNullOrEmpty()) {
return@withContext
}
for (metadata in wallpaper.toMetadata(context)) {
val localFile = File(context.filesDir.absolutePath, metadata.localPath)
if (localFile.exists()) continue
val request = Request(
url = "$remoteHost/${metadata.remotePath}",
method = Request.Method.GET
)
Result.runCatching {
val response = client.fetch(request)
if (!response.isSuccess) {
logger.error("Download response failure code: ${response.status}")
return@withContext
}
File(localFile.path.substringBeforeLast("/")).mkdirs()
response.body.useStream { input ->
input.copyTo(localFile.outputStream())
}
}.onFailure {
Result.runCatching {
if (localFile.exists()) {
localFile.delete()
}
}.onFailure { e ->
logger.error("Failed to delete stale wallpaper bitmaps while downloading", e)
}
logger.error(it.message ?: "Download failed: no throwable message included.", it)
}
}
}
private data class WallpaperMetadata(val remotePath: String, val localPath: String)
private fun Wallpaper.toMetadata(context: Context): List<WallpaperMetadata> =
listOf("landscape", "portrait").flatMap { orientation ->
listOf("light", "dark").map { theme ->
val localPath = "wallpapers/$orientation/$theme/$name.png"
val remotePath = "${context.resolutionSegment()}/" +
"$orientation/" +
"$theme/" +
"${collection.name}/" +
"$name.png"
WallpaperMetadata(remotePath, localPath)
}
}
@Suppress("MagicNumber")
private fun Context.resolutionSegment(): String = when (resources.displayMetrics.densityDpi) {
// targeting hdpi and greater density resolutions https://developer.android.com/training/multiscreen/screendensities
in 0..240 -> "low"
in 240..320 -> "medium"
else -> "high"
}
}

View File

@ -0,0 +1,62 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.wallpapers
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class LegacyWallpaperFileManager(
private val rootDirectory: File,
coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
private val scope = CoroutineScope(coroutineDispatcher)
private val portraitDirectory = File(rootDirectory, "wallpapers/portrait")
private val landscapeDirectory = File(rootDirectory, "wallpapers/landscape")
/**
* Lookup all the files for a wallpaper name. This lookup will fail if there are not
* files for each of the following orientation and theme combinations:
* light/portrait - light/landscape - dark/portrait - dark/landscape
*/
suspend fun lookupExpiredWallpaper(name: String): Wallpaper? = withContext(Dispatchers.IO) {
if (getAllLocalWallpaperPaths(name).all { File(rootDirectory, it).exists() }) {
Wallpaper(
name = name,
collection = Wallpaper.DefaultCollection,
textColor = null,
cardColor = null,
)
} else null
}
private fun getAllLocalWallpaperPaths(name: String): List<String> =
listOf("landscape", "portrait").flatMap { orientation ->
listOf("light", "dark").map { theme ->
Wallpaper.legacyGetLocalPath(orientation, theme, name)
}
}
/**
* Remove all wallpapers that are not the [currentWallpaper] or in [availableWallpapers].
*/
fun clean(currentWallpaper: Wallpaper, availableWallpapers: List<Wallpaper>) {
scope.launch {
val wallpapersToKeep = (listOf(currentWallpaper) + availableWallpapers).map { it.name }
cleanChildren(portraitDirectory, wallpapersToKeep)
cleanChildren(landscapeDirectory, wallpapersToKeep)
}
}
private fun cleanChildren(dir: File, wallpapersToKeep: List<String>) {
for (file in dir.walkTopDown()) {
if (file.isDirectory || file.nameWithoutExtension in wallpapersToKeep) continue
file.delete()
}
}
}

View File

@ -74,7 +74,23 @@ data class Wallpaper(
* @param theme One of dark/light. * @param theme One of dark/light.
* @param name The name of the wallpaper. * @param name The name of the wallpaper.
*/ */
fun getBaseLocalPath(orientation: String, theme: String, name: String): String = fun legacyGetLocalPath(orientation: String, theme: String, name: String): String =
"wallpapers/$orientation/$theme/$name.png" "wallpapers/$orientation/$theme/$name.png"
/**
* Defines the standard path at which a wallpaper resource is kept on disk.
*
* @param type The type of image that should be retrieved.
* @param name The name of the wallpaper.
*/
fun getLocalPath(type: ImageType, name: String) = "wallpapers/$name/${type.lowercase()}.png"
}
enum class ImageType {
Portrait,
Landscape,
Thumbnail;
fun lowercase(): String = this.name.lowercase()
} }
} }

View File

@ -4,7 +4,7 @@
package org.mozilla.fenix.wallpapers package org.mozilla.fenix.wallpapers
import android.content.Context import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Client
@ -12,34 +12,33 @@ import mozilla.components.concept.fetch.Request
import mozilla.components.concept.fetch.isSuccess import mozilla.components.concept.fetch.isSuccess
import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.wallpapers.Wallpaper.Companion.getLocalPath
import java.io.File import java.io.File
/** /**
* Can download wallpapers from a remote host. * Can download wallpapers from a remote host.
* *
* @param context Required for writing files to local storage. * @param filesDir The top level app-local storage directory.
* @param client Required for fetching files from network. * @param client Required for fetching files from network.
*/ */
class WallpaperDownloader( class WallpaperDownloader(
private val context: Context, private val filesDir: File,
private val client: Client, private val client: Client,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) { ) {
private val logger = Logger("WallpaperDownloader")
private val remoteHost = BuildConfig.WALLPAPER_URL private val remoteHost = BuildConfig.WALLPAPER_URL
/** /**
* Downloads a wallpaper from the network. Will try to fetch 4 versions of each wallpaper: * Downloads a wallpaper from the network. Will try to fetch 2 versions of each wallpaper:
* portrait/light - portrait/dark - landscape/light - landscape/dark. These are expected to be * portrait and landscape. These are expected to be found at a remote path in the form:
* found at a remote path in the form: * <WALLPAPER_URL>/<collection name>/<wallpaper name>/<orientation>.png
* <WALLPAPER_URL>/<resolution>/<orientation>/<app theme>/<wallpaper theme>/<wallpaper name>.png * and will be stored in the local path:
* wallpapers/<wallpaper name>/<orientation>.png
*/ */
suspend fun downloadWallpaper(wallpaper: Wallpaper) = withContext(Dispatchers.IO) { suspend fun downloadWallpaper(wallpaper: Wallpaper) = withContext(dispatcher) {
if (remoteHost.isNullOrEmpty()) { for (metadata in wallpaper.toMetadata()) {
return@withContext val localFile = File(filesDir.absolutePath, metadata.localPath)
} // Don't overwrite an asset if it exists
for (metadata in wallpaper.toMetadata(context)) {
val localFile = File(context.filesDir.absolutePath, metadata.localPath)
if (localFile.exists()) continue if (localFile.exists()) continue
val request = Request( val request = Request(
url = "$remoteHost/${metadata.remotePath}", url = "$remoteHost/${metadata.remotePath}",
@ -48,7 +47,6 @@ class WallpaperDownloader(
Result.runCatching { Result.runCatching {
val response = client.fetch(request) val response = client.fetch(request)
if (!response.isSuccess) { if (!response.isSuccess) {
logger.error("Download response failure code: ${response.status}")
return@withContext return@withContext
} }
File(localFile.path.substringBeforeLast("/")).mkdirs() File(localFile.path.substringBeforeLast("/")).mkdirs()
@ -56,39 +54,22 @@ class WallpaperDownloader(
input.copyTo(localFile.outputStream()) input.copyTo(localFile.outputStream())
} }
}.onFailure { }.onFailure {
// This should clean up any partial downloads.
Result.runCatching { Result.runCatching {
if (localFile.exists()) { if (localFile.exists()) {
localFile.delete() localFile.delete()
} }
}.onFailure { e ->
logger.error("Failed to delete stale wallpaper bitmaps while downloading", e)
} }
logger.error(it.message ?: "Download failed: no throwable message included.", it)
} }
} }
} }
private data class WallpaperMetadata(val remotePath: String, val localPath: String) private data class WallpaperMetadata(val remotePath: String, val localPath: String)
private fun Wallpaper.toMetadata(context: Context): List<WallpaperMetadata> = private fun Wallpaper.toMetadata(): List<WallpaperMetadata> =
listOf("landscape", "portrait").flatMap { orientation -> listOf(Wallpaper.ImageType.Portrait, Wallpaper.ImageType.Landscape).map { orientation ->
listOf("light", "dark").map { theme -> val localPath = getLocalPath(orientation, this.name)
val localPath = "wallpapers/$orientation/$theme/$name.png" val remotePath = "${collection.name}/${this.name}/${orientation.lowercase()}.png"
val remotePath = "${context.resolutionSegment()}/" +
"$orientation/" +
"$theme/" +
"${collection.name}/" +
"$name.png"
WallpaperMetadata(remotePath, localPath) WallpaperMetadata(remotePath, localPath)
} }
}
@Suppress("MagicNumber")
private fun Context.resolutionSegment(): String = when (resources.displayMetrics.densityDpi) {
// targeting hdpi and greater density resolutions https://developer.android.com/training/multiscreen/screendensities
in 0..240 -> "low"
in 240..320 -> "medium"
else -> "high"
}
} }

View File

@ -9,6 +9,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.mozilla.fenix.wallpapers.Wallpaper.Companion.getLocalPath
import java.io.File import java.io.File
class WallpaperFileManager( class WallpaperFileManager(
@ -16,13 +17,11 @@ class WallpaperFileManager(
coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO
) { ) {
private val scope = CoroutineScope(coroutineDispatcher) private val scope = CoroutineScope(coroutineDispatcher)
private val portraitDirectory = File(rootDirectory, "wallpapers/portrait") private val wallpapersDirectory = File(rootDirectory, "wallpapers")
private val landscapeDirectory = File(rootDirectory, "wallpapers/landscape")
/** /**
* Lookup all the files for a wallpaper name. This lookup will fail if there are not * Lookup all the files for a wallpaper name. This lookup will fail if there are not
* files for each of the following orientation and theme combinations: * files for each of a portrait and landscape orientation as well as a thumbnail.
* light/portrait - light/landscape - dark/portrait - dark/landscape
*/ */
suspend fun lookupExpiredWallpaper(name: String): Wallpaper? = withContext(Dispatchers.IO) { suspend fun lookupExpiredWallpaper(name: String): Wallpaper? = withContext(Dispatchers.IO) {
if (getAllLocalWallpaperPaths(name).all { File(rootDirectory, it).exists() }) { if (getAllLocalWallpaperPaths(name).all { File(rootDirectory, it).exists() }) {
@ -36,10 +35,8 @@ class WallpaperFileManager(
} }
private fun getAllLocalWallpaperPaths(name: String): List<String> = private fun getAllLocalWallpaperPaths(name: String): List<String> =
listOf("landscape", "portrait").flatMap { orientation -> Wallpaper.ImageType.values().map { orientation ->
listOf("light", "dark").map { theme -> getLocalPath(orientation, name)
Wallpaper.getBaseLocalPath(orientation, theme, name)
}
} }
/** /**
@ -48,15 +45,11 @@ class WallpaperFileManager(
fun clean(currentWallpaper: Wallpaper, availableWallpapers: List<Wallpaper>) { fun clean(currentWallpaper: Wallpaper, availableWallpapers: List<Wallpaper>) {
scope.launch { scope.launch {
val wallpapersToKeep = (listOf(currentWallpaper) + availableWallpapers).map { it.name } val wallpapersToKeep = (listOf(currentWallpaper) + availableWallpapers).map { it.name }
cleanChildren(portraitDirectory, wallpapersToKeep) for (file in wallpapersDirectory.listFiles()?.toList() ?: listOf()) {
cleanChildren(landscapeDirectory, wallpapersToKeep) if (file.isDirectory && !wallpapersToKeep.contains(file.name)) {
} file.deleteRecursively()
} }
}
private fun cleanChildren(dir: File, wallpapersToKeep: List<String>) {
for (file in dir.walkTopDown()) {
if (file.isDirectory || file.nameWithoutExtension in wallpapersToKeep) continue
file.delete()
} }
} }
} }

View File

@ -46,13 +46,13 @@ class WallpapersUseCases(
val initialize: InitializeWallpapersUseCase by lazy { val initialize: InitializeWallpapersUseCase by lazy {
// Required to even access context.filesDir property and to retrieve current locale // Required to even access context.filesDir property and to retrieve current locale
val (fileManager, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { val (fileManager, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
val fileManager = WallpaperFileManager(context.filesDir) val fileManager = LegacyWallpaperFileManager(context.filesDir)
val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag() val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag()
?: LocaleManager.getSystemDefault().toLanguageTag() ?: LocaleManager.getSystemDefault().toLanguageTag()
fileManager to currentLocale fileManager to currentLocale
} }
val downloader = WallpaperDownloader(context, client) val downloader = LegacyWallpaperDownloader(context, client)
DefaultInitializeWallpaperUseCase( LegacyInitializeWallpaperUseCase(
store = store, store = store,
downloader = downloader, downloader = downloader,
fileManager = fileManager, fileManager = fileManager,
@ -60,7 +60,7 @@ class WallpapersUseCases(
currentLocale = currentLocale currentLocale = currentLocale
) )
} }
val loadBitmap: LoadBitmapUseCase by lazy { DefaultLoadBitmapUseCase(context) } val loadBitmap: LoadBitmapUseCase by lazy { LegacyLoadBitmapUseCase(context) }
val selectWallpaper: SelectWallpaperUseCase by lazy { DefaultSelectWallpaperUseCase(context.settings(), store) } val selectWallpaper: SelectWallpaperUseCase by lazy { DefaultSelectWallpaperUseCase(context.settings(), store) }
/** /**
@ -75,10 +75,10 @@ class WallpapersUseCases(
} }
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultInitializeWallpaperUseCase( internal class LegacyInitializeWallpaperUseCase(
private val store: AppStore, private val store: AppStore,
private val downloader: WallpaperDownloader, private val downloader: LegacyWallpaperDownloader,
private val fileManager: WallpaperFileManager, private val fileManager: LegacyWallpaperFileManager,
private val settings: Settings, private val settings: Settings,
private val currentLocale: String, private val currentLocale: String,
private val possibleWallpapers: List<Wallpaper> = allWallpapers, private val possibleWallpapers: List<Wallpaper> = allWallpapers,
@ -192,7 +192,7 @@ class WallpapersUseCases(
} }
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultLoadBitmapUseCase(private val context: Context) : LoadBitmapUseCase { internal class LegacyLoadBitmapUseCase(private val context: Context) : LoadBitmapUseCase {
/** /**
* Load the bitmap for a [wallpaper], if available. * Load the bitmap for a [wallpaper], if available.
* *
@ -241,7 +241,7 @@ class WallpapersUseCases(
private fun Wallpaper.getLocalPathFromContext(context: Context): String { private fun Wallpaper.getLocalPathFromContext(context: Context): String {
val orientation = if (context.isLandscape()) "landscape" else "portrait" val orientation = if (context.isLandscape()) "landscape" else "portrait"
val theme = if (context.isDark()) "dark" else "light" val theme = if (context.isDark()) "dark" else "light"
return Wallpaper.getBaseLocalPath(orientation, theme, name) return Wallpaper.legacyGetLocalPath(orientation, theme, name)
} }
private fun Context.isLandscape(): Boolean { private fun Context.isLandscape(): Boolean {

View File

@ -0,0 +1,111 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.wallpapers
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class LegacyWallpaperFileManagerTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private lateinit var portraitLightFolder: File
private lateinit var portraitDarkFolder: File
private lateinit var landscapeLightFolder: File
private lateinit var landscapeDarkFolder: File
private val dispatcher = UnconfinedTestDispatcher()
private lateinit var fileManager: LegacyWallpaperFileManager
@Before
fun setup() {
portraitLightFolder = tempFolder.newFolder("wallpapers", "portrait", "light")
portraitDarkFolder = tempFolder.newFolder("wallpapers", "portrait", "dark")
landscapeLightFolder = tempFolder.newFolder("wallpapers", "landscape", "light")
landscapeDarkFolder = tempFolder.newFolder("wallpapers", "landscape", "dark")
fileManager = LegacyWallpaperFileManager(
rootDirectory = tempFolder.root,
coroutineDispatcher = dispatcher,
)
}
@Test
fun `GIVEN files exist in all directories WHEN expired wallpaper looked up THEN expired wallpaper returned`() = runTest {
val wallpaperName = "name"
createAllFiles(wallpaperName)
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
val expected = generateWallpaper(name = wallpaperName)
assertEquals(expected, result)
}
@Test
fun `GIVEN any missing file in directories WHEN expired wallpaper looked up THEN null returned`() = runTest {
val wallpaperName = "name"
File(landscapeLightFolder, "$wallpaperName.png").createNewFile()
File(landscapeDarkFolder, "$wallpaperName.png").createNewFile()
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
assertEquals(null, result)
}
@Test
fun `WHEN cleaned THEN current wallpaper and available wallpapers kept`() {
val currentName = "current"
val currentWallpaper = generateWallpaper(name = currentName)
val availableName = "available"
val available = generateWallpaper(name = availableName)
val unavailableName = "unavailable"
createAllFiles(currentName)
createAllFiles(availableName)
createAllFiles(unavailableName)
fileManager.clean(currentWallpaper, listOf(available))
assertTrue(getAllFiles(currentName).all { it.exists() })
assertTrue(getAllFiles(availableName).all { it.exists() })
assertTrue(getAllFiles(unavailableName).none { it.exists() })
}
private fun createAllFiles(name: String) {
for (file in getAllFiles(name)) {
file.createNewFile()
}
}
private fun getAllFiles(name: String): List<File> {
return listOf(
File(portraitLightFolder, "$name.png"),
File(portraitDarkFolder, "$name.png"),
File(landscapeLightFolder, "$name.png"),
File(landscapeDarkFolder, "$name.png"),
)
}
private fun generateWallpaper(name: String) = Wallpaper(
name = name,
textColor = null,
cardColor = null,
collection = Wallpaper.Collection(
name = Wallpaper.defaultName,
heading = null,
description = null,
availableLocales = null,
startDate = null,
endDate = null,
learnMoreUrl = null
),
)
}

View File

@ -0,0 +1,137 @@
package org.mozilla.fenix.wallpapers
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import mozilla.components.concept.fetch.Client
import mozilla.components.concept.fetch.Request
import mozilla.components.concept.fetch.Response
import mozilla.components.concept.fetch.isSuccess
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.mozilla.fenix.BuildConfig
import java.io.File
import java.lang.IllegalStateException
class WallpaperDownloaderTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private val remoteHost = BuildConfig.WALLPAPER_URL
private val wallpaperBytes = "file contents"
private val portraitResponseBodySuccess = Response.Body(wallpaperBytes.byteInputStream())
private val landscapeResponseBodySuccess = Response.Body(wallpaperBytes.byteInputStream())
private val mockPortraitResponse = mockk<Response>()
private val mockLandscapeResponse = mockk<Response>()
private val mockClient = mockk<Client>()
private val dispatcher = UnconfinedTestDispatcher()
private val wallpaperCollection = Wallpaper.Collection(
name = "collection",
heading = null,
description = null,
learnMoreUrl = null,
availableLocales = null,
startDate = null,
endDate = null
)
private lateinit var downloader: WallpaperDownloader
@Before
fun setup() {
downloader = WallpaperDownloader(tempFolder.root, mockClient, dispatcher)
}
@Test
fun `GIVEN that request is successful WHEN downloading THEN file is created in expected location`() = runTest {
val wallpaper = generateWallpaper()
val portraitRequest = Request(
url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/portrait.png",
method = Request.Method.GET
)
val landscapeRequest = Request(
url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/landscape.png",
method = Request.Method.GET
)
every { mockPortraitResponse.status } returns 200
every { mockLandscapeResponse.status } returns 200
every { mockPortraitResponse.body } returns portraitResponseBodySuccess
every { mockLandscapeResponse.body } returns landscapeResponseBodySuccess
every { mockClient.fetch(portraitRequest) } returns mockPortraitResponse
every { mockClient.fetch(landscapeRequest) } returns mockLandscapeResponse
downloader.downloadWallpaper(wallpaper)
val expectedPortraitFile = File(tempFolder.root, "wallpapers/${wallpaper.name}/portrait.png")
val expectedLandscapeFile = File(tempFolder.root, "wallpapers/${wallpaper.name}/landscape.png")
assertTrue(expectedPortraitFile.exists() && expectedPortraitFile.readText() == wallpaperBytes)
assertTrue(expectedLandscapeFile.exists() && expectedLandscapeFile.readText() == wallpaperBytes)
}
@Test
fun `GIVEN that request fails WHEN downloading THEN file is not created`() = runTest {
val wallpaper = generateWallpaper()
val portraitRequest = Request(
url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/portrait.png",
method = Request.Method.GET
)
val landscapeRequest = Request(
url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/landscape.png",
method = Request.Method.GET
)
every { mockPortraitResponse.status } returns 400
every { mockLandscapeResponse.status } returns 400
every { mockClient.fetch(portraitRequest) } returns mockPortraitResponse
every { mockClient.fetch(landscapeRequest) } returns mockLandscapeResponse
downloader.downloadWallpaper(wallpaper)
val expectedPortraitFile = File(tempFolder.root, "wallpapers/${wallpaper.name}/portrait.png")
val expectedLandscapeFile = File(tempFolder.root, "wallpapers/${wallpaper.name}/landscape.png")
assertFalse(expectedPortraitFile.exists())
assertFalse(expectedLandscapeFile.exists())
}
@Test
fun `GIVEN that copying the file fails WHEN downloading THEN file is not created`() = runTest {
val wallpaper = generateWallpaper()
val portraitRequest = Request(
url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/portrait.png",
method = Request.Method.GET
)
val landscapeRequest = Request(
url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/landscape.png",
method = Request.Method.GET
)
every { mockPortraitResponse.status } returns 200
every { mockLandscapeResponse.status } returns 200
every { mockPortraitResponse.body } throws IllegalStateException()
every { mockClient.fetch(portraitRequest) } throws IllegalStateException()
every { mockClient.fetch(landscapeRequest) } returns mockLandscapeResponse
downloader.downloadWallpaper(wallpaper)
val expectedPortraitFile = File(tempFolder.root, "wallpapers/${wallpaper.name}/portrait.png")
val expectedLandscapeFile = File(tempFolder.root, "wallpapers/${wallpaper.name}/landscape.png")
assertFalse(expectedPortraitFile.exists())
assertFalse(expectedLandscapeFile.exists())
}
private fun generateWallpaper(name: String = "name") = Wallpaper(
name = name,
collection = wallpaperCollection,
textColor = null,
cardColor = null
)
}

View File

@ -1,12 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.wallpapers package org.mozilla.fenix.wallpapers
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -18,10 +14,7 @@ class WallpaperFileManagerTest {
@Rule @Rule
@JvmField @JvmField
val tempFolder = TemporaryFolder() val tempFolder = TemporaryFolder()
private lateinit var portraitLightFolder: File private lateinit var wallpapersFolder: File
private lateinit var portraitDarkFolder: File
private lateinit var landscapeLightFolder: File
private lateinit var landscapeDarkFolder: File
private val dispatcher = UnconfinedTestDispatcher() private val dispatcher = UnconfinedTestDispatcher()
@ -29,10 +22,7 @@ class WallpaperFileManagerTest {
@Before @Before
fun setup() { fun setup() {
portraitLightFolder = tempFolder.newFolder("wallpapers", "portrait", "light") wallpapersFolder = File(tempFolder.root, "wallpapers")
portraitDarkFolder = tempFolder.newFolder("wallpapers", "portrait", "dark")
landscapeLightFolder = tempFolder.newFolder("wallpapers", "landscape", "light")
landscapeDarkFolder = tempFolder.newFolder("wallpapers", "landscape", "dark")
fileManager = WallpaperFileManager( fileManager = WallpaperFileManager(
rootDirectory = tempFolder.root, rootDirectory = tempFolder.root,
coroutineDispatcher = dispatcher, coroutineDispatcher = dispatcher,
@ -40,25 +30,65 @@ class WallpaperFileManagerTest {
} }
@Test @Test
fun `GIVEN files exist in all directories WHEN expired wallpaper looked up THEN expired wallpaper returned`() = runTest { fun `GIVEN wallpaper directory exists WHEN looked up THEN wallpaper created with correct name`() = runTest {
val wallpaperName = "name" val wallpaperName = "name"
createAllFiles(wallpaperName) createAllFiles(wallpaperName)
val result = fileManager.lookupExpiredWallpaper(wallpaperName) val result = fileManager.lookupExpiredWallpaper(wallpaperName)
val expected = generateWallpaper(name = wallpaperName) val expected = generateWallpaper(name = wallpaperName)
assertEquals(expected, result) Assert.assertEquals(expected, result)
} }
@Test @Test
fun `GIVEN any missing file in directories WHEN expired wallpaper looked up THEN null returned`() = runTest { fun `GIVEN portrait file missing in directories WHEN expired wallpaper looked up THEN null returned`() = runTest {
val wallpaperName = "name" val wallpaperName = "name"
File(landscapeLightFolder, "$wallpaperName.png").createNewFile() File(wallpapersFolder, "$wallpaperName/landscape.png").apply {
File(landscapeDarkFolder, "$wallpaperName.png").createNewFile() mkdirs()
createNewFile()
}
File(wallpapersFolder, "$wallpaperName/thumbnail.png").apply {
mkdirs()
createNewFile()
}
val result = fileManager.lookupExpiredWallpaper(wallpaperName) val result = fileManager.lookupExpiredWallpaper(wallpaperName)
assertEquals(null, result) Assert.assertEquals(null, result)
}
@Test
fun `GIVEN landscape file missing in directories WHEN expired wallpaper looked up THEN null returned`() = runTest {
val wallpaperName = "name"
File(wallpapersFolder, "$wallpaperName/portrait.png").apply {
mkdirs()
createNewFile()
}
File(wallpapersFolder, "$wallpaperName/thumbnail.png").apply {
mkdirs()
createNewFile()
}
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
Assert.assertEquals(null, result)
}
@Test
fun `GIVEN thumbnail file missing in directories WHEN expired wallpaper looked up THEN null returned`() = runTest {
val wallpaperName = "name"
File(wallpapersFolder, "$wallpaperName/portrait.png").apply {
mkdirs()
createNewFile()
}
File(wallpapersFolder, "$wallpaperName/landscape.png").apply {
mkdirs()
createNewFile()
}
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
Assert.assertEquals(null, result)
} }
@Test @Test
@ -81,16 +111,18 @@ class WallpaperFileManagerTest {
private fun createAllFiles(name: String) { private fun createAllFiles(name: String) {
for (file in getAllFiles(name)) { for (file in getAllFiles(name)) {
file.mkdirs()
file.createNewFile() file.createNewFile()
} }
} }
private fun getAllFiles(name: String): List<File> { private fun getAllFiles(name: String): List<File> {
val folder = File(wallpapersFolder, name)
return listOf( return listOf(
File(portraitLightFolder, "$name.png"), folder,
File(portraitDarkFolder, "$name.png"), File(folder, "portrait.png"),
File(landscapeLightFolder, "$name.png"), File(folder, "landscape.png"),
File(landscapeDarkFolder, "$name.png"), File(folder, "thumbnail.png"),
) )
} }
@ -98,14 +130,6 @@ class WallpaperFileManagerTest {
name = name, name = name,
textColor = null, textColor = null,
cardColor = null, cardColor = null,
collection = Wallpaper.Collection( collection = Wallpaper.DefaultCollection
name = Wallpaper.defaultName,
heading = null,
description = null,
availableLocales = null,
startDate = null,
endDate = null,
learnMoreUrl = null
),
) )
} }

View File

@ -37,8 +37,8 @@ class WallpapersUseCasesTest {
private val appStore = AppStore() private val appStore = AppStore()
private val mockSettings = mockk<Settings>() private val mockSettings = mockk<Settings>()
private val mockDownloader = mockk<WallpaperDownloader>(relaxed = true) private val mockDownloader = mockk<LegacyWallpaperDownloader>(relaxed = true)
private val mockFileManager = mockk<WallpaperFileManager> { private val mockFileManager = mockk<LegacyWallpaperFileManager> {
every { clean(any(), any()) } just runs every { clean(any(), any()) } just runs
} }
@ -50,7 +50,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns "" every { mockSettings.currentWallpaper } returns ""
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,
@ -71,7 +71,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns "" every { mockSettings.currentWallpaper } returns ""
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,
@ -96,7 +96,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns "" every { mockSettings.currentWallpaper } returns ""
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,
@ -120,7 +120,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns expiredWallpaper.name every { mockSettings.currentWallpaper } returns expiredWallpaper.name
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,
@ -143,7 +143,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns "" every { mockSettings.currentWallpaper } returns ""
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,
@ -164,7 +164,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns "" every { mockSettings.currentWallpaper } returns ""
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,
@ -186,7 +186,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns "" every { mockSettings.currentWallpaper } returns ""
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,
@ -209,7 +209,7 @@ class WallpapersUseCasesTest {
every { mockSettings.currentWallpaper } returns selectedWallpaper.name every { mockSettings.currentWallpaper } returns selectedWallpaper.name
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.DefaultInitializeWallpaperUseCase( WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore, appStore,
mockDownloader, mockDownloader,
mockFileManager, mockFileManager,