Skip to content

Commit

Permalink
More fixes for image loading
Browse files Browse the repository at this point in the history
Fix: State issues with image loading on the import screen
Fix: Cache only using file name intead of url
Improvement: Encode URLs to a UUID instead of hash to avoid accidental hits
Feat: Option to also delete instance .minecraft on options screen
  • Loading branch information
0ffz committed Mar 7, 2024
1 parent ec6c1a2 commit e60f729
Show file tree
Hide file tree
Showing 12 changed files with 62 additions and 33 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
group=com.mineinabyss
version=2.0.0-alpha.5
version=2.0.0-alpha.6
idofrontVersion=0.22.3
11 changes: 6 additions & 5 deletions src/main/kotlin/com/mineinabyss/launchy/data/Dirs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@ import kotlin.io.path.*

object Dirs {
val home = Path(System.getProperty("user.home"))

val minecraft = when (OS.get()) {
OS.WINDOWS -> Path(System.getenv("APPDATA")) / ".minecraft"
OS.MAC -> Path(System.getProperty("user.home")) / "Library/Application Support/minecraft"
OS.LINUX -> Path(System.getProperty("user.home")) / ".minecraft"
OS.MAC -> home / "Library/Application Support/minecraft"
OS.LINUX -> home / ".minecraft"
}

val mineinabyss = when (OS.get()) {
OS.WINDOWS -> Path(System.getenv("APPDATA")) / ".mineinabyss"
OS.MAC -> Path(System.getProperty("user.home")) / "Library/Application Support/mineinabyss"
OS.LINUX -> Path(System.getProperty("user.home")) / ".mineinabyss"
OS.MAC -> home / "Library/Application Support/mineinabyss"
OS.LINUX -> home / ".mineinabyss"
}

val config = when (OS.get()) {
OS.WINDOWS -> Path(System.getenv("APPDATA"))
OS.MAC -> Path(System.getProperty("user.home")) / "Library/Application Support"
OS.MAC -> home / "Library/Application Support"
OS.LINUX -> home / ".config"
} / "mineinabyss"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import com.mineinabyss.launchy.data.Dirs
import com.mineinabyss.launchy.data.Formats
import com.mineinabyss.launchy.data.modpacks.source.PackSource
import com.mineinabyss.launchy.logic.Downloader
import com.mineinabyss.launchy.state.LaunchyState
import com.mineinabyss.launchy.logic.urlToFileName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
Expand All @@ -30,20 +33,22 @@ data class GameInstanceConfig(
val overrideMinecraftDir: String? = null,
) {
@Transient
val backgroundPath = Dirs.imageCache / "background-${backgroundURL.hashCode().toHexString()}"
val backgroundPath = Dirs.imageCache / "background-${urlToFileName(backgroundURL)}"

@Transient
val logoPath = Dirs.imageCache / "icon-${logoURL.hashCode().toHexString()}"
val logoPath = Dirs.imageCache / "icon-${urlToFileName(logoURL)}"

@Transient
private var cachedBackground = mutableStateOf<BitmapPainter?>(null)

@Transient
private var cachedLogo = mutableStateOf<BitmapPainter?>(null)

@OptIn(ExperimentalCoroutinesApi::class)
@Transient
val downloadScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))

private suspend fun loadBackground() {
if (cachedBackground.value != null) return
runCatching {
Downloader.download(backgroundURL, backgroundPath, override = false)
val painter = BitmapPainter(loadImageBitmap(backgroundPath.inputStream()))
Expand All @@ -52,7 +57,6 @@ data class GameInstanceConfig(
}

private suspend fun loadLogo() {
if (cachedLogo.value != null) return
runCatching {
Downloader.download(logoURL, logoPath, override = false)
val painter = BitmapPainter(loadImageBitmap(logoPath.inputStream()))
Expand All @@ -61,16 +65,16 @@ data class GameInstanceConfig(
}

@Composable
fun getBackground(state: LaunchyState) = remember {
fun getBackground() = remember {
cachedBackground.also {
if (it.value == null) state.ioScope.launch { loadBackground() }
if (it.value == null) downloadScope.launch { loadBackground() }
}
}

@Composable
fun getLogo(state: LaunchyState) = remember {
fun getLogo() = remember {
cachedLogo.also {
if (it.value == null) state.ioScope.launch { loadLogo() }
if (it.value == null) downloadScope.launch { loadLogo() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import androidx.compose.ui.res.loadImageBitmap
import com.mineinabyss.launchy.data.Dirs
import com.mineinabyss.launchy.data.serializers.UUIDSerializer
import com.mineinabyss.launchy.logic.Downloader
import com.mineinabyss.launchy.state.LaunchyState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
Expand All @@ -25,14 +27,20 @@ data class PlayerProfile(
@Transient
private val avatar = mutableStateOf<BitmapPainter?>(null)

@OptIn(ExperimentalCoroutinesApi::class)
@Transient
private val downloadScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))

@Composable
fun getAvatar(state: LaunchyState): MutableState<BitmapPainter?> = remember {
fun getAvatar(): MutableState<BitmapPainter?> = remember {
avatar.also {
if (it.value != null) return@also
state.ioScope.launch {
downloadScope.launch {
Downloader.downloadAvatar(uuid)
it.value =
BitmapPainter(loadImageBitmap(Dirs.avatar(uuid).inputStream()), filterQuality = FilterQuality.None)
it.value = BitmapPainter(
loadImageBitmap(Dirs.avatar(uuid).inputStream()),
filterQuality = FilterQuality.None
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object Downloader {
val lastModified = headers["Last-Modified"]?.fromHttpToGmtDate()?.timestamp?.toHexString()
val length = headers["Content-Length"]?.toLongOrNull()?.toHexString()
val cache = "Last-Modified: $lastModified, Content-Length: $length"
val cacheFile = Dirs.cacheDir / "${writeTo.name}.cache"
val cacheFile = Dirs.cacheDir / "${urlToFileName(url)}.cache"
if (writeTo.exists() && cacheFile.exists() && cacheFile.readText() == cache) return@runCatching
cacheFile.createParentDirectories()
cacheFile.deleteIfExists()
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/com/mineinabyss/launchy/logic/Helpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ package com.mineinabyss.launchy.logic

import com.mineinabyss.launchy.ui.screens.Dialog
import com.mineinabyss.launchy.ui.screens.dialog
import java.util.*

fun <T> Result<T>.showDialogOnError(title: String? = null): Result<T> {
onFailure { dialog = Dialog.fromException(it, title) }
return this
}

fun <T> Result<T>.getOrShowDialog() = showDialogOnError().getOrNull()


fun urlToFileName(url: String): String {
return UUID.nameUUIDFromBytes(url.toByteArray()).toString()
}
3 changes: 2 additions & 1 deletion src/main/kotlin/com/mineinabyss/launchy/logic/Instances.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import kotlin.io.path.deleteRecursively

object Instances {
@OptIn(ExperimentalPathApi::class)
fun GameInstance.delete(state: LaunchyState) {
fun GameInstance.delete(state: LaunchyState, deleteDotMinecraft: Boolean) {
try {
state.inProgressTasks["deleteInstance"] = InProgressTask("Deleting instance ${config.name}")
state.gameInstances.remove(this)
state.ioScope.launch {
if (deleteDotMinecraft) minecraftDir.deleteRecursively()
configDir.deleteRecursively()
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.mineinabyss.launchy.data.config.PlayerProfile
@Composable
fun PlayerAvatar(profile: PlayerProfile, modifier: Modifier = Modifier) {
val state = LocalLaunchyState
val avatar: BitmapPainter? by profile.getAvatar(state)
val avatar: BitmapPainter? by profile.getAvatar()
if (avatar != null) Image(
painter = avatar!!,
contentDescription = "Avatar",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fun InstanceCard(
) {
val state = LocalLaunchyState
val coroutineScope = rememberCoroutineScope()
val background by config.getBackground(state)
val background by config.getBackground()
Card(
onClick = {
instance ?: return@Card
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.mineinabyss.launchy.data.config.GameInstance
import com.mineinabyss.launchy.data.config.GameInstanceConfig
import com.mineinabyss.launchy.logic.Downloader
import com.mineinabyss.launchy.logic.showDialogOnError
import com.mineinabyss.launchy.logic.urlToFileName
import com.mineinabyss.launchy.state.InProgressTask
import com.mineinabyss.launchy.ui.elements.AnimatedTab
import com.mineinabyss.launchy.ui.elements.ComfyContent
Expand Down Expand Up @@ -85,8 +86,8 @@ fun NewInstance() {
TextButton(onClick = {
urlValid = urlValid()
if (!urlValid) return@TextButton
val taskKey = "import-cloud-instance-${urlText.hashCode()}"
val downloadPath = Dirs.tmp / "launchy-cloud-instance-${urlText.hashCode()}.yml"
val taskKey = "import-cloud-instance-${urlToFileName(urlText)}"
val downloadPath = Dirs.tmp / "launchy-cloud-instance-${urlToFileName(urlText)}.yml"
downloadPath.deleteIfExists()
coroutineScope.launch(Dispatchers.IO) {
state.inProgressTasks[taskKey] = InProgressTask("Importing cloud instance")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import com.mineinabyss.launchy.ui.screens.LocalModpackState
fun BoxScope.BackgroundImage(windowScope: WindowScope) {
val pack = LocalModpackState
val state = LocalLaunchyState
val background by pack.instance.config.getBackground(state)
val background by pack.instance.config.getBackground()
AnimatedVisibility(background != null, enter = fadeIn(), exit = fadeOut()) {
if (background == null) return@AnimatedVisibility
windowScope.WindowDraggableArea {
Expand Down Expand Up @@ -78,7 +78,7 @@ fun BoxScope.SlightBackgroundTint(modifier: Modifier = Modifier) {
fun LogoLarge(modifier: Modifier) {
val state = LocalLaunchyState
val pack = LocalModpackState
val painter by pack.instance.config.getLogo(state)
val painter by pack.instance.config.getLogo()
AnimatedVisibility(
painter != null,
enter = fadeIn() + expandVertically(clip = false) + fadeIn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,19 @@ fun OptionsTab() {
ComfyContent(Modifier.padding(16.dp)) {
Column {
TitleSmall("Danger zone")
TextButton(onClick = {
screen = Screen.Default
pack.instance.delete(state)
}) {
Text("Delete Instance", color = MaterialTheme.colorScheme.primary)
Row {
TextButton(onClick = {
screen = Screen.Default
pack.instance.delete(state, deleteDotMinecraft = false)
}) {
Text("Delete Instance from config", color = MaterialTheme.colorScheme.primary)
}
TextButton(onClick = {
screen = Screen.Default
pack.instance.delete(state, deleteDotMinecraft = true)
}) {
Text("Delete Instance and its .minecraft", color = MaterialTheme.colorScheme.error)
}
}
}
}
Expand Down

0 comments on commit e60f729

Please sign in to comment.