From 5877a74844d920465516d38fc63d8d22c8305737 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 12 Apr 2025 20:49:18 -0400 Subject: [PATCH 1/3] Use ktor instead of fuel --- common/build.gradle.kts | 14 ++--- .../lambda/module/modules/client/Discord.kt | 7 ++- .../lambda/module/modules/client/Network.kt | 13 ++--- .../kotlin/com/lambda/network/CapeManager.kt | 53 +++++++++---------- .../kotlin/com/lambda/network/LambdaHttp.kt | 44 +++++++++++++++ .../network/api/v1/endpoints/GetCape.kt | 15 +++--- .../network/api/v1/endpoints/LinkDiscord.kt | 22 ++++---- .../lambda/network/api/v1/endpoints/Login.kt | 18 ++++--- .../network/api/v1/endpoints/SetCape.kt | 18 +++---- fabric/build.gradle.kts | 12 ++--- forge/build.gradle.kts | 22 +++----- gradle.properties | 3 +- 12 files changed, 136 insertions(+), 105 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/network/LambdaHttp.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index d72e6c4ec..baf593e2b 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -21,8 +21,7 @@ val modId: String by project val fabricLoaderVersion: String by project val kotlinxCoroutinesVersion: String by project val discordIPCVersion: String by project -val fuelVersion: String by project -val resultVersion: String by project +val ktorVersion: String by project val mockitoKotlin: String by project val mockitoInline: String by project val mockkVersion: String by project @@ -50,16 +49,19 @@ dependencies { implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") implementation("com.pngencoder:pngencoder:0.15.0") - // Fuel HTTP library and dependencies - implementation("com.github.kittinunf.fuel:fuel:$fuelVersion") - implementation("com.github.kittinunf.fuel:fuel-gson:$fuelVersion") - implementation("com.github.kittinunf.result:result-jvm:$resultVersion") + // Ktor + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-gson:$ktorVersion") // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") // Baritone modImplementation("baritone-api:baritone-unoptimized-fabric:1.10.2") { isTransitive = false } + + // Test implementations testImplementation(kotlin("test")) testImplementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlin") testImplementation("org.mockito:mockito-inline:$mockitoInline") diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/Discord.kt b/common/src/main/kotlin/com/lambda/module/modules/client/Discord.kt index 3dfcbb0d1..a00866cde 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/Discord.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/Discord.kt @@ -79,10 +79,9 @@ object Discord : Module( val auth = rpc.applicationManager.authenticate() - linkDiscord(discordToken = auth.accessToken, - success = { updateToken(it); discordAuth = auth }, - failure = { warn("Failed to link the discord account to the minecraft auth") } - ) + linkDiscord(discordToken = auth.accessToken) + .fold(onSuccess = { updateToken(it); discordAuth = auth }, + onFailure = { warn("Failed to link the discord account to the minecraft auth") }) } private fun stop() { diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/Network.kt b/common/src/main/kotlin/com/lambda/module/modules/client/Network.kt index 6a17e3464..74ac5917f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/Network.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/Network.kt @@ -67,17 +67,18 @@ object Network : Module( hash = BigInteger(computed).toString(16) } - listenUnsafe { + listenUnsafeConcurrently { // FixMe: If the player have the properties but are invalid this doesn't work - if (NetworkManager.isValid || mc.gameProfile.isOffline) return@listenUnsafe + if (NetworkManager.isValid || mc.gameProfile.isOffline) return@listenUnsafeConcurrently // If we log in right as the client responds to the encryption request, we start // a race condition where the game server haven't acknowledged the packets // and posted to the sessionserver api - login(mc.session.username, hash ?: return@listenUnsafe, - success = { updateToken(it) }, - failure = { LOG.warn("Unable to authenticate: $it") } - ) + login(mc.session.username, hash ?: return@listenUnsafeConcurrently) + .fold( + onSuccess = { updateToken(it) }, + onFailure = { LOG.warn("Unable to authenticate: $it") } + ) } } diff --git a/common/src/main/kotlin/com/lambda/network/CapeManager.kt b/common/src/main/kotlin/com/lambda/network/CapeManager.kt index 93873b3c4..25d688e91 100644 --- a/common/src/main/kotlin/com/lambda/network/CapeManager.kt +++ b/common/src/main/kotlin/com/lambda/network/CapeManager.kt @@ -17,8 +17,6 @@ package com.lambda.network -import com.github.kittinunf.fuel.Fuel -import com.github.kittinunf.fuel.core.requests.CancellableRequest import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.core.Loadable @@ -29,13 +27,13 @@ import com.lambda.network.api.v1.endpoints.getCape import com.lambda.network.api.v1.endpoints.setCape import com.lambda.network.api.v1.models.Cape import com.lambda.sound.SoundManager.toIdentifier -import com.lambda.util.Communication.info -import com.lambda.util.Communication.logError +import com.lambda.threading.runIO import com.lambda.util.FolderRegister.capes import com.lambda.util.extension.get import com.lambda.util.extension.resolveFile import net.minecraft.client.texture.NativeImage.read import net.minecraft.client.texture.NativeImageBackedTexture +import java.io.ByteArrayOutputStream import java.util.UUID import java.util.concurrent.ConcurrentHashMap import kotlin.io.path.ExperimentalPathApi @@ -58,36 +56,35 @@ object CapeManager : ConcurrentHashMap(), Loadable { /** * Sets the current player's cape */ - fun SafeContext.updateCape(cape: String): CancellableRequest = - setCape(cape, - success = { fetchCape(player.uuid); info("Successfully update your cape to $cape") }, - failure = { logError("Could not update the player cape", it) } - ) + fun updateCape(cape: String, block: () -> Unit = {}) = runIO { + setCape(cape) + }.invokeOnCompletion { block() } /** * Fetches the cape of the given player id */ - fun SafeContext.fetchCape(uuid: UUID): CancellableRequest = - getCape(uuid, - success = { mc.textureManager.get(it.identifier) ?: download(it); put(uuid, it.id) }, - failure = { logError("Could not fetch the cape of the player", it) } - ) + fun SafeContext.fetchCape(uuid: UUID, block: () -> Unit = {}) = runIO { + val cape = getCape(uuid).getOrThrow() - private fun SafeContext.download(cape: Cape): CancellableRequest = - Fuel.download(cape.url) - .fileDestination { _, _ -> capes.resolveFile("${cape.id}.png") } - .response { result -> - result.fold( - success = { - val image = TextureUtils.readImage(it) - val native = NativeImageBackedTexture(image) - val id = cape.identifier + mc.textureManager.get(cape.identifier) ?: download(cape) + put(uuid, cape.id) + }.invokeOnCompletion { block() } - mc.textureManager.registerTexture(id, native) - }, - failure = { logError("Could not download the cape", it) } - ) - } + private fun SafeContext.download(cape: Cape, block: () -> Unit = {}) = runIO { + val destination = capes.resolveFile("${cape.id}.png") + val output = ByteArrayOutputStream() + + LambdaHttp.download(cape.url, output) + + val bytes = output.toByteArray() + destination.writeBytes(bytes) + + val image = TextureUtils.readImage(bytes) + val native = NativeImageBackedTexture(image) + val id = cape.identifier + + mc.textureManager.registerTexture(id, native) + }.invokeOnCompletion { block() } override fun load() = "Loaded ${images.size} cached capes" diff --git a/common/src/main/kotlin/com/lambda/network/LambdaHttp.kt b/common/src/main/kotlin/com/lambda/network/LambdaHttp.kt new file mode 100644 index 000000000..743f4240d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/network/LambdaHttp.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.network + +import com.lambda.Lambda +import io.ktor.client.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.gson.* +import java.io.File +import java.io.OutputStream + +val LambdaHttp = HttpClient { + install(ContentNegotiation) { + // Use our gson instance + register(ContentType.Application.Json, GsonConverter(Lambda.gson)) + } +} + +suspend inline fun HttpClient.download(url: String, file: File, block: HttpRequestBuilder.() -> Unit = {}) = + file.writeBytes(get(url, block).readRawBytes()) + +suspend inline fun HttpClient.download(url: String, output: OutputStream, block: HttpRequestBuilder.() -> Unit = {}) = + output.write(get(url, block).readRawBytes()) + +suspend inline fun HttpClient.download(url: String, block: HttpRequestBuilder.() -> Unit) = + get(url, block).readRawBytes() diff --git a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt index 323eebe44..f1f710b57 100644 --- a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt +++ b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCape.kt @@ -17,15 +17,12 @@ package com.lambda.network.api.v1.endpoints -import com.github.kittinunf.fuel.Fuel -import com.github.kittinunf.fuel.core.FuelError -import com.github.kittinunf.fuel.core.ResultHandler -import com.github.kittinunf.fuel.gson.responseObject -import com.github.kittinunf.result.failure -import com.github.kittinunf.result.success import com.lambda.module.modules.client.Network.apiUrl import com.lambda.module.modules.client.Network.apiVersion +import com.lambda.network.LambdaHttp import com.lambda.network.api.v1.models.Cape +import io.ktor.client.call.* +import io.ktor.client.request.* import java.util.UUID /** @@ -36,6 +33,6 @@ import java.util.UUID * * response: [Cape] or error */ -fun getCape(uuid: UUID, success: (Cape) -> Unit, failure: (FuelError) -> Unit) = - Fuel.get("$apiUrl/api/${apiVersion.value}/cape?id=$uuid") - .responseObject { _, _, result -> result.fold(success, failure) } +suspend fun getCape(uuid: UUID) = runCatching { + LambdaHttp.get("$apiUrl/api/${apiVersion.value}/cape?id=$uuid").body() +} diff --git a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt index 3f36e79f0..9808719fd 100644 --- a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt +++ b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/LinkDiscord.kt @@ -17,15 +17,14 @@ package com.lambda.network.api.v1.endpoints -import com.github.kittinunf.fuel.Fuel -import com.github.kittinunf.fuel.core.FuelError -import com.github.kittinunf.fuel.core.extensions.authentication -import com.github.kittinunf.fuel.core.extensions.jsonBody -import com.github.kittinunf.fuel.gson.responseObject import com.lambda.module.modules.client.Network.apiUrl import com.lambda.module.modules.client.Network.apiVersion +import com.lambda.network.LambdaHttp import com.lambda.network.NetworkManager import com.lambda.network.api.v1.models.Authentication +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* /** * Links a Discord account to a session account @@ -35,9 +34,10 @@ import com.lambda.network.api.v1.models.Authentication * * response: [Authentication] or error */ -fun linkDiscord(discordToken: String, success: (Authentication) -> Unit, failure: (FuelError) -> Unit) = - Fuel.post("${apiUrl}/api/${apiVersion.value}/link/discord") - .jsonBody("""{ "token": "$discordToken" }""") - .authentication() - .bearer(NetworkManager.accessToken) - .responseObject { _, _, result -> result.fold(success, failure) } +suspend fun linkDiscord(discordToken: String) = runCatching { + LambdaHttp.post("${apiUrl}/api/${apiVersion.value}/link/discord") { + setBody("""{ "token": "$discordToken" }""") + bearerAuth(NetworkManager.accessToken) + contentType(ContentType.Application.Json) + }.body() +} diff --git a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt index ecf2c76cd..60987a780 100644 --- a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt +++ b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/Login.kt @@ -17,13 +17,13 @@ package com.lambda.network.api.v1.endpoints -import com.github.kittinunf.fuel.Fuel -import com.github.kittinunf.fuel.core.FuelError -import com.github.kittinunf.fuel.core.extensions.jsonBody -import com.github.kittinunf.fuel.gson.responseObject import com.lambda.module.modules.client.Network.apiUrl import com.lambda.module.modules.client.Network.apiVersion +import com.lambda.network.LambdaHttp import com.lambda.network.api.v1.models.Authentication +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* /** * Creates a new session account with mojang session hashes @@ -34,7 +34,9 @@ import com.lambda.network.api.v1.models.Authentication * * response: [Authentication] or error */ -fun login(username: String, hash: String, success: (Authentication) -> Unit, failure: (FuelError) -> Unit) = - Fuel.post("${apiUrl}/api/${apiVersion.value}/login") - .jsonBody("""{ "username": "$username", "hash": "$hash" }""") - .responseObject { _, _, result -> result.fold(success, failure) } +suspend fun login(username: String, hash: String) = runCatching { + LambdaHttp.post("${apiUrl}/api/${apiVersion.value}/login") { + setBody("""{ "username": "$username", "hash": "$hash" }""") + contentType(ContentType.Application.Json) + }.body() +} diff --git a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt index 71af19127..5e724a1f7 100644 --- a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt +++ b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt @@ -17,13 +17,12 @@ package com.lambda.network.api.v1.endpoints -import com.github.kittinunf.fuel.Fuel -import com.github.kittinunf.fuel.core.FuelError -import com.github.kittinunf.fuel.core.awaitResult -import com.github.kittinunf.fuel.core.extensions.authentication import com.lambda.module.modules.client.Network.apiUrl import com.lambda.module.modules.client.Network.apiVersion +import com.lambda.network.LambdaHttp import com.lambda.network.NetworkManager +import io.ktor.client.request.* +import io.ktor.http.* /** * Sets the currently authenticated player's cape @@ -33,8 +32,9 @@ import com.lambda.network.NetworkManager * * response: [Unit] or error */ -fun setCape(id: String, success: (ByteArray) -> Unit, failure: (FuelError) -> Unit) = - Fuel.put("$apiUrl/api/${apiVersion.value}/cape?id=$id") - .authentication() - .bearer(NetworkManager.accessToken) - .response { _, _, resp -> resp.fold(success, failure) } +suspend fun setCape(id: String) = runCatching { + LambdaHttp.put("$apiUrl/api/${apiVersion.value}/cape?id=$id") { + bearerAuth(NetworkManager.accessToken) + contentType(ContentType.Application.Json) + } +} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index cc392d797..7c94f28f5 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -22,8 +22,7 @@ val fabricApiVersion: String by project val kotlinFabricVersion: String by project val discordIPCVersion: String by project val kotlinVersion: String by project -val fuelVersion: String by project -val resultVersion: String by project +val ktorVersion: String by project base.archivesName = "${base.archivesName.get()}-fabric" @@ -90,10 +89,11 @@ dependencies { includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") - // Fuel HTTP library and dependencies - includeLib("com.github.kittinunf.fuel:fuel:$fuelVersion") - includeLib("com.github.kittinunf.fuel:fuel-gson:$fuelVersion") - includeLib("com.github.kittinunf.result:result-jvm:$resultVersion") + // Ktor + includeLib("io.ktor:ktor-client-core:$ktorVersion") + shadowBundle("io.ktor:ktor-client-cio:$ktorVersion") { exclude(group = "org.jetbrains.kotlin"); exclude(group = "org.jetbrains.kotlinx"); exclude(group = "org.slf4j") } + includeLib("io.ktor:ktor-client-content-negotiation:$ktorVersion") + includeLib("io.ktor:ktor-serialization-gson:$ktorVersion") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 60f2b208b..fd05d97e6 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -22,8 +22,7 @@ val forgeVersion: String by project val mixinExtrasVersion: String by project val kotlinForgeVersion: String by project val discordIPCVersion: String by project -val fuelVersion: String by project -val resultVersion: String by project +val ktorVersion: String by project base.archivesName = "${base.archivesName.get()}-forge" @@ -102,10 +101,11 @@ dependencies { includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") - // Fuel HTTP library and dependencies - includeLib("com.github.kittinunf.fuel:fuel:$fuelVersion") - includeLib("com.github.kittinunf.fuel:fuel-gson:$fuelVersion") - includeLib("com.github.kittinunf.result:result-jvm:$resultVersion") + // Ktor + includeLib("io.ktor:ktor-client-core:$ktorVersion") + shadowBundle("io.ktor:ktor-client-cio:$ktorVersion") { exclude(group = "org.jetbrains.kotlin"); exclude(group = "org.jetbrains.kotlinx"); exclude(group = "org.slf4j") } + includeLib("io.ktor:ktor-client-content-negotiation:$ktorVersion") + includeLib("io.ktor:ktor-serialization-gson:$ktorVersion") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") @@ -127,16 +127,6 @@ dependencies { } tasks { - // Merge the resources and classes into the same directory. - // This is done because java expects modules to be in a single directory. - // And if we have it in multiple we have to do performance intensive hacks like having the UnionFileSystem - // This will eventually be migrated to ForgeGradle so modders don't need to manually do it. But that is later. - sourceSets.forEach { - val dir = layout.buildDirectory.dir("sourcesSets/${it.name}") - it.output.setResourcesDir(dir) - it.java.destinationDirectory.set(dir) - } - shadowJar { archiveVersion = "$modVersion+$minecraftVersion" configurations = listOf(shadowBundle) diff --git a/gradle.properties b/gradle.properties index ed9e77f2a..8404dd9f0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,8 +34,7 @@ kotlinxCoroutinesVersion=1.10.2 javaVersion=17 baritoneVersion=1.10.2 discordIPCVersion=8edf2dbeda -fuelVersion=2.3.1 -resultVersion=5.6.0 +ktorVersion=3.1.2 mockitoKotlin=5.4.0 mockitoInline=5.2.0 mockkVersion=1.13.17 From 288cb465e54476c0cb23d3fb07171c59d2866ba1 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 12 Apr 2025 21:19:07 -0400 Subject: [PATCH 2/3] More shadow configurations --- fabric/build.gradle.kts | 20 +++++++++++++------- forge/build.gradle.kts | 20 +++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 7c94f28f5..14879e877 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -60,22 +60,28 @@ val common: Configuration by configurations.creating { val includeLib: Configuration by configurations.creating val includeMod: Configuration by configurations.creating -val shadowBundle: Configuration by configurations.creating { - isCanBeResolved = true - isCanBeConsumed = false -} +val shadowLib: Configuration by configurations.creating { isCanBeConsumed = false } +val shadowMod: Configuration by configurations.creating { isCanBeConsumed = false } +val shadowBundle: Configuration by configurations.creating { isCanBeConsumed = false } fun DependencyHandlerScope.setupConfigurations() { includeLib.dependencies.forEach { implementation(it) include(it) - // shadowBundle(it) } includeMod.dependencies.forEach { modImplementation(it) // include(it) } + + shadowLib.dependencies.forEach { + implementation(it) + } + + shadowMod.dependencies.forEach { + modImplementation(it) + } } dependencies { @@ -91,7 +97,7 @@ dependencies { // Ktor includeLib("io.ktor:ktor-client-core:$ktorVersion") - shadowBundle("io.ktor:ktor-client-cio:$ktorVersion") { exclude(group = "org.jetbrains.kotlin"); exclude(group = "org.jetbrains.kotlinx"); exclude(group = "org.slf4j") } + shadowLib("io.ktor:ktor-client-cio:$ktorVersion") { exclude(group = "org.jetbrains.kotlin"); exclude(group = "org.jetbrains.kotlinx"); exclude(group = "org.slf4j") } includeLib("io.ktor:ktor-client-content-negotiation:$ktorVersion") includeLib("io.ktor:ktor-serialization-gson:$ktorVersion") @@ -111,7 +117,7 @@ dependencies { tasks { shadowJar { archiveVersion = "$modVersion+$minecraftVersion" - configurations = listOf(shadowBundle) + configurations = listOf(shadowLib, shadowMod, shadowBundle) archiveClassifier = "dev-shadow" } diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index fd05d97e6..0b5224ed3 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -73,22 +73,28 @@ val common: Configuration by configurations.creating { val includeLib: Configuration by configurations.creating val includeMod: Configuration by configurations.creating -val shadowBundle: Configuration by configurations.creating { - isCanBeResolved = true - isCanBeConsumed = false -} +val shadowLib: Configuration by configurations.creating { isCanBeConsumed = false } +val shadowMod: Configuration by configurations.creating { isCanBeConsumed = false } +val shadowBundle: Configuration by configurations.creating { isCanBeConsumed = false } fun DependencyHandlerScope.setupConfigurations() { includeLib.dependencies.forEach { implementation(it) include(it) - // shadowBundle(it) } includeMod.dependencies.forEach { implementation(it) // include(it) } + + shadowLib.dependencies.forEach { + implementation(it) + } + + shadowMod.dependencies.forEach { + implementation(it) + } } dependencies { @@ -103,7 +109,7 @@ dependencies { // Ktor includeLib("io.ktor:ktor-client-core:$ktorVersion") - shadowBundle("io.ktor:ktor-client-cio:$ktorVersion") { exclude(group = "org.jetbrains.kotlin"); exclude(group = "org.jetbrains.kotlinx"); exclude(group = "org.slf4j") } + shadowLib("io.ktor:ktor-client-cio:$ktorVersion") { exclude(group = "org.jetbrains.kotlin"); exclude(group = "org.jetbrains.kotlinx"); exclude(group = "org.slf4j") } includeLib("io.ktor:ktor-client-content-negotiation:$ktorVersion") includeLib("io.ktor:ktor-serialization-gson:$ktorVersion") @@ -129,7 +135,7 @@ dependencies { tasks { shadowJar { archiveVersion = "$modVersion+$minecraftVersion" - configurations = listOf(shadowBundle) + configurations = listOf(shadowLib, shadowMod, shadowBundle) archiveClassifier = "dev-shadow" } From eb614773de976ee5b3ef0ab897ad7323c66b6385 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 12 Apr 2025 21:19:23 -0400 Subject: [PATCH 3/3] Better error handling --- .../lambda/command/commands/CapeCommand.kt | 10 ++++++---- .../kotlin/com/lambda/network/CapeManager.kt | 20 ++++++++++--------- .../network/api/v1/endpoints/SetCape.kt | 4 +++- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/command/commands/CapeCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/CapeCommand.kt index 6214ff182..e5eff981b 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/CapeCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/CapeCommand.kt @@ -25,7 +25,8 @@ import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.network.CapeManager.updateCape import com.lambda.network.NetworkManager -import com.lambda.threading.runSafe +import com.lambda.util.Communication.info +import com.lambda.util.Communication.logError import com.lambda.util.extension.CommandBuilder object CapeCommand : LambdaCommand( @@ -44,9 +45,10 @@ object CapeCommand : LambdaCommand( } execute { - runSafe { - val cape = id().value() - updateCape(cape) + val cape = id().value() + updateCape(cape) { error -> + if (error != null) logError("Could not update your cape", error) + else info("Updated your cape to $cape") } } } diff --git a/common/src/main/kotlin/com/lambda/network/CapeManager.kt b/common/src/main/kotlin/com/lambda/network/CapeManager.kt index 25d688e91..b11ba3ce8 100644 --- a/common/src/main/kotlin/com/lambda/network/CapeManager.kt +++ b/common/src/main/kotlin/com/lambda/network/CapeManager.kt @@ -36,13 +36,11 @@ import net.minecraft.client.texture.NativeImageBackedTexture import java.io.ByteArrayOutputStream import java.util.UUID import java.util.concurrent.ConcurrentHashMap -import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.extension import kotlin.io.path.inputStream import kotlin.io.path.nameWithoutExtension import kotlin.io.path.walk -@OptIn(ExperimentalPathApi::class) @Suppress("JavaIoSerializableObjectMustHaveReadResolve") object CapeManager : ConcurrentHashMap(), Loadable { /** @@ -55,22 +53,26 @@ object CapeManager : ConcurrentHashMap(), Loadable { /** * Sets the current player's cape + * + * @param block Lambda called once the coroutine completes, it contains the throwable if any */ - fun updateCape(cape: String, block: () -> Unit = {}) = runIO { - setCape(cape) - }.invokeOnCompletion { block() } + fun updateCape(cape: String, block: (Throwable?) -> Unit = {}) = runIO { + setCape(cape).getOrThrow() + }.invokeOnCompletion { block(it) } /** * Fetches the cape of the given player id + * + * @param block Lambda called once the coroutine completes, it contains the throwable if any */ - fun SafeContext.fetchCape(uuid: UUID, block: () -> Unit = {}) = runIO { + fun SafeContext.fetchCape(uuid: UUID, block: (Throwable?) -> Unit = {}) = runIO { val cape = getCape(uuid).getOrThrow() mc.textureManager.get(cape.identifier) ?: download(cape) put(uuid, cape.id) - }.invokeOnCompletion { block() } + }.invokeOnCompletion { block(it) } - private fun SafeContext.download(cape: Cape, block: () -> Unit = {}) = runIO { + private fun SafeContext.download(cape: Cape, block: (Throwable?) -> Unit = {}) = runIO { val destination = capes.resolveFile("${cape.id}.png") val output = ByteArrayOutputStream() @@ -84,7 +86,7 @@ object CapeManager : ConcurrentHashMap(), Loadable { val id = cape.identifier mc.textureManager.registerTexture(id, native) - }.invokeOnCompletion { block() } + }.invokeOnCompletion { block(it) } override fun load() = "Loaded ${images.size} cached capes" diff --git a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt index 5e724a1f7..1fec095ae 100644 --- a/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt +++ b/common/src/main/kotlin/com/lambda/network/api/v1/endpoints/SetCape.kt @@ -33,8 +33,10 @@ import io.ktor.http.* * response: [Unit] or error */ suspend fun setCape(id: String) = runCatching { - LambdaHttp.put("$apiUrl/api/${apiVersion.value}/cape?id=$id") { + val resp = LambdaHttp.put("$apiUrl/api/${apiVersion.value}/cape?id=$id") { bearerAuth(NetworkManager.accessToken) contentType(ContentType.Application.Json) } + + check(resp.status == HttpStatusCode.OK) }