Skip to content

Commit

Permalink
For mozilla-mobile#26424 - Create wallpaper file migration helper
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandru2909 committed Sep 1, 2022
1 parent a7b4134 commit 82c334a
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 32 deletions.
8 changes: 8 additions & 0 deletions app/src/main/java/org/mozilla/fenix/utils/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = { mr2022Sections[Mr2022Section.WALLPAPERS_SELECTION_TOOL] == true },
)

/**
* Indicates if the legacy wallpapers should be migrated to the V2 path.
*/
var shouldMigrateLegacyWallpapers by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_legacy_wallpaper_migrated),
default = true
)

var openLinksInAPrivateTab by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_open_links_in_a_private_tab),
default = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.wallpapers.Wallpaper.Companion.getLocalPath
import java.io.File

Expand All @@ -24,7 +25,6 @@ class WallpaperFileManager(
coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
private val scope = CoroutineScope(coroutineDispatcher)
private val wallpapersDirectory = File(storageRootDirectory, "wallpapers")

/**
* Lookup all the files for a wallpaper name. This lookup will fail if there are not
Expand All @@ -49,17 +49,98 @@ class WallpaperFileManager(
}

/**
* Remove all wallpapers that are not the [currentWallpaper] or in [availableWallpapers].
* Migrate the legacy wallpapers to the new path and delete the remaining legacy files.
*
* @param availableWallpapers List of wallpapers available remote.
* @param downloadThumbnail Suspend function used to download the thumbnail for a wallpaper
* available remote.
* @return List of migrated wallpapers.
*/
suspend fun clean(currentWallpaper: Wallpaper, availableWallpapers: List<Wallpaper>) = withContext(Dispatchers.IO) {
scope.launch {
val wallpapersToKeep = (listOf(currentWallpaper) + availableWallpapers).map { it.name }
wallpapersDirectory.listFiles()?.forEach { file ->
if (file.isDirectory && !wallpapersToKeep.contains(file.name)) {
file.deleteRecursively()
suspend fun migrateAndCleanLegacyWallpapers(
availableWallpapers: List<Wallpaper>,
downloadThumbnail: suspend (Wallpaper) -> Wallpaper.ImageFileState
): List<Wallpaper> = withContext(Dispatchers.IO) {
val legacyPortraitDirectory = File(storageRootDirectory, "wallpapers/portrait/light")
val legacyLandscapeDirectory = File(storageRootDirectory, "wallpapers/landscape/light")

val legacyWallpapers = legacyPortraitDirectory.listFiles()
?.filter { file -> !file.isDirectory }
?.mapNotNull { portraitFile ->
val landscapeFile = File(legacyLandscapeDirectory, portraitFile.name)
if (landscapeFile.exists()) {
Pair(portraitFile, landscapeFile)
} else {
null
}
}

val migratedWallpapers = legacyWallpapers?.map { pair ->
val name = pair.first.nameWithoutExtension
availableWallpapers.firstOrNull { it.name == name } ?: Wallpaper(
name = name,
collection = Wallpaper.DefaultCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
)
}

legacyWallpapers?.let {
migrateLegacyWallpapers(availableWallpapers, it, downloadThumbnail)
}
migratedWallpapers ?: listOf()
}

@Suppress("TooGenericExceptionCaught")
private fun migrateLegacyWallpapers(
availableWallpapers: List<Wallpaper>,
legacyWallpapers: List<Pair<File, File>>,
downloadThumbnail: suspend (Wallpaper) -> Wallpaper.ImageFileState
) = scope.launch {
legacyWallpapers.forEach { pair ->
val portraitFile = pair.first
val landscapeFile = pair.second
val name = portraitFile.nameWithoutExtension
val directory = "wallpapers/${name.lowercase()}"
val remoteWallpaper = availableWallpapers.firstOrNull { it.name == name }

try {
// If there is a thumbnail available for the current wallpaper,
// attempt to download it
if (remoteWallpaper == null ||
downloadThumbnail(remoteWallpaper) == Wallpaper.ImageFileState.Error
) {
// If there is no thumbnail available for the wallpaper locally or remote,
// use the portrait file as thumbnail
portraitFile.copyTo(
File(
storageRootDirectory,
"$directory/thumbnail.png"
)
)
}
// Copy the portrait file
portraitFile.copyTo(
File(
storageRootDirectory,
"$directory/portrait.png"
)
)
// Copy the landscape file
landscapeFile.copyTo(
File(
storageRootDirectory,
"$directory/landscape.png"
)
)
} catch (e: Exception) {
Logger.error("Failed to migrate legacy wallpaper", e)
}
}
// Delete the remaining legacy files
File(storageRootDirectory, "wallpapers/portrait").deleteRecursively()
File(storageRootDirectory, "wallpapers/landscape").deleteRecursively()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ class WallpapersUseCases(
store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(it))
}
val currentWallpaperName = withContext(Dispatchers.IO) { settings.currentWallpaperName }
val possibleWallpapers = metadataFetcher.downloadWallpaperList().filter {
val wallpaperList = metadataFetcher.downloadWallpaperList()
val possibleWallpapers = wallpaperList.filter {
!it.isExpired() && it.isAvailableInLocale()
}
val currentWallpaper = possibleWallpapers.find { it.name == currentWallpaperName }
Expand All @@ -248,17 +249,24 @@ class WallpapersUseCases(
// dispatched above, we may still need to update other metadata about it.
store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(currentWallpaper))

fileManager.clean(
currentWallpaper,
possibleWallpapers
)

val wallpapersWithUpdatedThumbnailState = possibleWallpapers.map { wallpaper ->
val result = downloader.downloadThumbnail(wallpaper)
wallpaper.copy(thumbnailFileState = result)
}

val defaultIncluded = listOf(Wallpaper.Default) + wallpapersWithUpdatedThumbnailState
val migratedWallpapers =
if (settings.shouldMigrateLegacyWallpapers) {
settings.shouldMigrateLegacyWallpapers = false
fileManager.migrateAndCleanLegacyWallpapers(
availableWallpapers = wallpaperList,
downloadThumbnail = downloader::downloadThumbnail
)
} else {
listOf()
}

val defaultIncluded = listOf(Wallpaper.Default) + wallpapersWithUpdatedThumbnailState +
migratedWallpapers
store.dispatch(AppAction.WallpaperAction.UpdateAvailableWallpapers(defaultIncluded))
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/preference_keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
<string name="pref_key_current_wallpaper_text_color" translatable="false">pref_key_current_wallpaper_text_color</string>
<string name="pref_key_current_wallpaper_card_color" translatable="false">pref_key_current_wallpaper_card_color</string>
<string name="pref_key_wallpapers_onboarding" translatable="false">pref_key_wallpapers_onboarding</string>
<string name="pref_key_legacy_wallpaper_migrated" translatable="false">pref_key_legacy_wallpaper_migrated</string>

<string name="pref_key_encryption_key_generated" translatable="false">pref_key_encryption_key_generated</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class WallpaperFileManagerTest {
private val dispatcher = UnconfinedTestDispatcher()

private lateinit var fileManager: WallpaperFileManager
private lateinit var portraitLightFolder: File
private lateinit var portraitDarkFolder: File
private lateinit var landscapeLightFolder: File
private lateinit var landscapeDarkFolder: File

@Before
fun setup() {
Expand All @@ -28,6 +32,10 @@ class WallpaperFileManagerTest {
storageRootDirectory = tempFolder.root,
coroutineDispatcher = dispatcher,
)
portraitLightFolder = tempFolder.newFolder("wallpapers", "portrait", "light")
portraitDarkFolder = tempFolder.newFolder("wallpapers", "portrait", "dark")
landscapeLightFolder = tempFolder.newFolder("wallpapers", "landscape", "light")
landscapeDarkFolder = tempFolder.newFolder("wallpapers", "landscape", "dark")
}

@Test
Expand Down Expand Up @@ -93,21 +101,92 @@ class WallpaperFileManagerTest {
}

@Test
fun `WHEN cleaned THEN current wallpaper and available wallpapers kept`() = runTest {
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() })
fun `WHEN legacy wallpapers are migrated AND the thumbnail is not available on the remote server THEN the portrait file is used`() = runTest {
val firstWallpaper = "wallpaper1"
createAllLegacyFiles(firstWallpaper)

fileManager.migrateAndCleanLegacyWallpapers(listOf()) { Wallpaper.ImageFileState.Error }

assertTrue(getAllFiles(firstWallpaper).all { it.exists() })
assertTrue(
File(wallpapersFolder, "$firstWallpaper/portrait.png").readBytes()
.contentEquals(File(wallpapersFolder, "$firstWallpaper/thumbnail.png").readBytes())
)
}

@Test
fun `WHEN legacy wallpapers are migrated THEN the legacy wallpapers are deleted`() = runTest {
val firstWallpaper = "wallpaper1"
val secondWallpaper = "wallpaper2"
createAllLegacyFiles(firstWallpaper)
createAllLegacyFiles(secondWallpaper)

val migratedWallpapers = fileManager
.migrateAndCleanLegacyWallpapers(listOf()) { Wallpaper.ImageFileState.Downloaded }

assertEquals(2, migratedWallpapers.size)
val wallpaperNames = migratedWallpapers.map { wallpaper -> wallpaper.name }
assertTrue(wallpaperNames.contains(firstWallpaper))
assertTrue(wallpaperNames.contains(secondWallpaper))
assertTrue(getAllFiles(firstWallpaper).all { it.exists() })
assertTrue(getAllFiles(secondWallpaper).all { it.exists() })
assertFalse(File(portraitLightFolder, "$firstWallpaper.png").exists())
assertFalse(File(portraitDarkFolder, "$firstWallpaper.png").exists())
assertFalse(File(landscapeLightFolder, "$firstWallpaper.png").exists())
assertFalse(File(landscapeDarkFolder, "$firstWallpaper.png").exists())
}

@Test
fun `GIVEN landscape legacy wallpaper is missing WHEN the wallpapers are migrated THEN the wallpaper is not migrated`() =
runTest {
val portraitOnlyWallpaperName = "portraitOnly"
val allWallpaperName = "legacy"
File(landscapeLightFolder, "$portraitOnlyWallpaperName.png").apply {
createNewFile()
}
File(landscapeDarkFolder, "$portraitOnlyWallpaperName.png").apply {
createNewFile()
}
createAllLegacyFiles(allWallpaperName)

fileManager.migrateAndCleanLegacyWallpapers(listOf()) { Wallpaper.ImageFileState.Downloaded }

assertTrue(getAllFiles(allWallpaperName).all { it.exists() })
assertFalse(getAllFiles(portraitOnlyWallpaperName).any { it.exists() })
}

@Test
fun `GIVEN portrait legacy wallpaper is missing WHEN the wallpapers are migrated THEN the wallpaper is not migrated`() =
runTest {
val landscapeOnlyWallpaperName = "portraitOnly"
val allWallpaperName = "legacy"
File(portraitLightFolder, "$landscapeOnlyWallpaperName.png").apply {
createNewFile()
}
File(portraitDarkFolder, "$landscapeOnlyWallpaperName.png").apply {
createNewFile()
}
createAllLegacyFiles(allWallpaperName)

fileManager.migrateAndCleanLegacyWallpapers(listOf()) { Wallpaper.ImageFileState.Downloaded }

assertTrue(getAllFiles(allWallpaperName).all { it.exists() })
assertFalse(getAllFiles(landscapeOnlyWallpaperName).any { it.exists() })
}

private fun createAllLegacyFiles(name: String) {
File(portraitLightFolder, "$name.png").apply {
createNewFile()
}
File(landscapeLightFolder, "$name.png").apply {
createNewFile()
}
File(portraitDarkFolder, "$name.png").apply {
createNewFile()
}
File(landscapeDarkFolder, "$name.png").apply {
createNewFile()
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class WallpapersUseCasesTest {
every { currentWallpaperTextColor = any() } just Runs
every { currentWallpaperCardColor } returns 0L
every { currentWallpaperCardColor = any() } just Runs
every { shouldMigrateLegacyWallpapers } returns true
every { shouldMigrateLegacyWallpapers = any() } just Runs
}
private val mockLegacyDownloader = mockk<LegacyWallpaperDownloader>(relaxed = true)
private val mockLegacyFileManager = mockk<LegacyWallpaperFileManager> {
Expand All @@ -54,7 +56,7 @@ class WallpapersUseCasesTest {
coEvery { downloadWallpaper(any()) } returns mockk()
}
private val mockFileManager = mockk<WallpaperFileManager> {
coEvery { clean(any(), any()) } returns mockk()
coEvery { migrateAndCleanLegacyWallpapers(any(), any()) } returns listOf()
}

@Test
Expand Down Expand Up @@ -310,7 +312,6 @@ class WallpapersUseCasesTest {
val expectedFilteredWallpaper = fakeExpiredRemoteWallpapers[0]
appStore.waitUntilIdle()
assertFalse(appStore.state.wallpaperState.availableWallpapers.contains(expectedFilteredWallpaper))
coVerify { mockFileManager.clean(Wallpaper.Default, fakeRemoteWallpapers) }
}

@Test
Expand Down

0 comments on commit 82c334a

Please sign in to comment.