Skip to content

Commit 1c4e8ea

Browse files
committed
Bulk cape support
1 parent 3d98cbe commit 1c4e8ea

File tree

7 files changed

+67
-27
lines changed

7 files changed

+67
-27
lines changed

common/src/main/kotlin/com/lambda/command/commands/CapeCommand.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.lambda.brigadier.argument.value
2323
import com.lambda.brigadier.execute
2424
import com.lambda.brigadier.required
2525
import com.lambda.command.LambdaCommand
26+
import com.lambda.network.CapeManager
2627
import com.lambda.network.CapeManager.updateCape
2728
import com.lambda.network.NetworkManager
2829
import com.lambda.util.Communication.info
@@ -38,7 +39,7 @@ object CapeCommand : LambdaCommand(
3839
required(literal("set")) {
3940
required(string("id")) { id ->
4041
suggests { _, builder ->
41-
NetworkManager.capes
42+
CapeManager.capeList
4243
.forEach { builder.suggest(it) }
4344

4445
builder.buildFuture()

common/src/main/kotlin/com/lambda/module/modules/client/Network.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,11 @@ object Network : Module(
4949
defaultTags = setOf(ModuleTag.CLIENT),
5050
enabledByDefault = true,
5151
) {
52-
val authServer by setting("Auth Server", "auth.lambda-client.org")
53-
val apiUrl by setting("API Server", "https://api.lambda-client.org")
54-
val apiVersion by setting("API Version", ApiVersion.V1)
55-
val mappings by setting("Mappings", "https://mappings.lambda-client.org")
52+
val authServer by setting("Auth Server", "auth.lambda-client.org")
53+
val apiUrl by setting("API Server", "https://api.lambda-client.org")
54+
val apiVersion by setting("API Version", ApiVersion.V1)
55+
val mappings by setting("Mappings", "https://mappings.lambda-client.org")
56+
val cdn by setting("CDN", "https://cdn.lambda-client.org")
5657

5758
val gameVersion = SharedConstants.getGameVersion().name
5859

common/src/main/kotlin/com/lambda/network/CapeManager.kt

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,76 @@
1717

1818
package com.lambda.network
1919

20+
import com.lambda.Lambda.LOG
2021
import com.lambda.Lambda.mc
2122
import com.lambda.context.SafeContext
2223
import com.lambda.core.Loadable
2324
import com.lambda.event.events.WorldEvent
2425
import com.lambda.event.listener.SafeListener.Companion.listen
2526
import com.lambda.graphics.texture.TextureUtils
27+
import com.lambda.module.modules.client.Network.cdn
2628
import com.lambda.network.api.v1.endpoints.getCape
29+
import com.lambda.network.api.v1.endpoints.getCapes
2730
import com.lambda.network.api.v1.endpoints.setCape
2831
import com.lambda.sound.SoundManager.toIdentifier
2932
import com.lambda.threading.runIO
33+
import com.lambda.threading.runSafe
34+
import com.lambda.util.FileUtils.createIfNotExists
35+
import com.lambda.util.FileUtils.downloadCompare
3036
import com.lambda.util.FileUtils.downloadIfNotPresent
37+
import com.lambda.util.FileUtils.ifNotExists
38+
import com.lambda.util.FileUtils.isOlderThan
3139
import com.lambda.util.FolderRegister.capes
3240
import com.lambda.util.extension.resolveFile
41+
import kotlinx.coroutines.runBlocking
3342
import net.minecraft.client.texture.NativeImage.read
3443
import net.minecraft.client.texture.NativeImageBackedTexture
44+
import java.util.LinkedList
3545
import java.util.UUID
3646
import java.util.concurrent.ConcurrentHashMap
47+
import kotlin.concurrent.fixedRateTimer
3748
import kotlin.io.path.extension
3849
import kotlin.io.path.inputStream
3950
import kotlin.io.path.nameWithoutExtension
4051
import kotlin.io.path.walk
52+
import kotlin.time.Duration.Companion.hours
53+
import kotlin.time.Duration.Companion.seconds
4154

4255
@Suppress("JavaIoSerializableObjectMustHaveReadResolve")
4356
object CapeManager : ConcurrentHashMap<UUID, String>(), Loadable {
44-
/**
45-
* We want to cache images to reduce cloudflare requests and save money
46-
*/
57+
// We want to cache images to reduce class B requests
4758
private val images = capes.walk()
4859
.filter { it.extension == "png" }
4960
.associate { it.nameWithoutExtension to NativeImageBackedTexture(read(it.inputStream())) }
5061
.onEach { (key, value) -> mc.textureManager.registerTexture(key.toIdentifier(), value) }
5162

63+
private val fetchQueue = LinkedList<UUID>()
64+
65+
// We want to cache the cape list to reduce class B requests
66+
val capeList = runBlocking {
67+
capes.resolveFile("capes.txt")
68+
.isOlderThan(24.hours) {
69+
it.downloadIfNotPresent("$cdn/capes.txt")
70+
.onFailure { err -> LOG.error("Could not download the cape list: $err") }
71+
}
72+
.ifNotExists {
73+
it.downloadCompare("$cdn/capes.txt", -1)
74+
.onFailure { err -> LOG.error("Could not download the cape list: $err") }
75+
}
76+
.createIfNotExists()
77+
.readText()
78+
.split("\n")
79+
}
80+
5281
/**
5382
* Sets the current player's cape
5483
*
5584
* @param block Lambda called once the coroutine completes, it contains the throwable if any
5685
*/
5786
fun updateCape(cape: String, block: (Throwable?) -> Unit = {}) = runIO {
5887
setCape(cape).getOrThrow()
88+
89+
runSafe { fetchCape(player.uuid) }
5990
}.invokeOnCompletion { block(it) }
6091

6192
/**
@@ -75,11 +106,24 @@ object CapeManager : ConcurrentHashMap<UUID, String>(), Loadable {
75106
put(uuid, cape.id)
76107
}.invokeOnCompletion { block(it) }
77108

78-
override fun load() = "Loaded ${images.size} cached capes"
109+
override fun load() = "Loaded ${images.size} cached capes and ${capeList.size} remote capes"
79110

80111
init {
81-
listen<WorldEvent.Player.Join>(alwaysListen = true) {
82-
fetchCape(it.uuid)
112+
fixedRateTimer(
113+
daemon = true,
114+
name = "Cape-fetcher",
115+
period = 15.seconds.inWholeMilliseconds,
116+
) {
117+
if (fetchQueue.isEmpty()) return@fixedRateTimer
118+
119+
runBlocking {
120+
getCapes(fetchQueue)
121+
.onSuccess { it.forEach { cape -> put(cape.uuid, cape.id) } }
122+
123+
fetchQueue.clear()
124+
}
83125
}
126+
127+
listen<WorldEvent.Player.Join>(alwaysListen = true) { fetchQueue.push(it.uuid) }
84128
}
85129
}

common/src/main/kotlin/com/lambda/network/NetworkManager.kt

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.lambda.config.configurations.UserConfig
2424
import com.lambda.core.Loadable
2525
import com.lambda.network.api.v1.models.Authentication
2626
import com.lambda.network.api.v1.models.Authentication.Data
27+
import com.lambda.util.FolderRegister.capes
2728
import com.lambda.util.reflections.getResources
2829
import java.io.File
2930
import java.util.*
@@ -52,11 +53,6 @@ object NetworkManager : Configurable(UserConfig), Loadable {
5253

5354
private var deserialized: Data? = null
5455

55-
// ToDo: Fetch remote file instead of checking local files
56-
val capes = getResources(".*.png")
57-
.filter { it.contains("capes") } // filterByInput hangs the program
58-
.map { File(it).nameWithoutExtension }
59-
6056
fun updateToken(resp: Authentication) {
6157
accessToken = resp.accessToken
6258
decodeAuth(accessToken)
@@ -67,11 +63,8 @@ object NetworkManager : Configurable(UserConfig), Loadable {
6763
deserialized = gson.fromJson(String(Base64.getUrlDecoder().decode(payload)), Data::class.java)
6864
}
6965

70-
override fun load(): String {
71-
decodeAuth(accessToken)
72-
73-
// ToDo: Re-authenticate every 24 hours
7466

75-
return "Loaded ${capes.size} capes"
67+
init {
68+
decodeAuth(accessToken)
7669
}
7770
}

common/src/main/kotlin/com/lambda/network/api/v1/endpoints/GetCapes.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.lambda.network.api.v1.endpoints
22

3-
import com.lambda.module.modules.client.Network
43
import com.lambda.module.modules.client.Network.apiUrl
54
import com.lambda.module.modules.client.Network.apiVersion
65
import com.lambda.network.LambdaHttp
@@ -20,7 +19,7 @@ import java.util.*
2019
*
2120
* @return results of capes
2221
*/
23-
suspend fun getCapes(vararg uuid: UUID) = getCapes(uuid.toSet())
22+
suspend fun getCapes(vararg uuid: UUID) = getCapes(uuid.toList())
2423

2524
/**
2625
* Gets the cape of the given player UUIDs
@@ -32,7 +31,7 @@ suspend fun getCapes(vararg uuid: UUID) = getCapes(uuid.toSet())
3231
*
3332
* @return results of capes
3433
*/
35-
suspend fun getCapes(uuids: Set<UUID>) = runCatching {
34+
suspend fun getCapes(uuids: List<UUID>) = runCatching {
3635
LambdaHttp.get("$apiUrl/api/$apiVersion/capes") {
3736
contentType(ContentType.Application.Json)
3837
setBody("""{ "players": [${uuids.joinToString(prefix = "\"", postfix = "\"", separator = "\",\"")}] }""")

common/src/main/kotlin/com/lambda/network/api/v1/models/Cape.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ import java.util.UUID
2424

2525
class Cape(
2626
@SerializedName("uuid")
27-
val uuid: String,
27+
val uuid: UUID,
2828

2929
@SerializedName("type")
3030
val id: String,
3131
) {
3232
val url: String
3333
get() = "https://cdn.lambda-client.org/$id.png"
34+
35+
override fun toString() = "Cape(uuid=$uuid, id=$id, url=$url)"
3436
}

common/src/main/kotlin/com/lambda/util/FileUtils.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ object FileUtils {
6464
/**
6565
* Executes the [block] if the file is older than the given [duration]
6666
*/
67-
fun File.isOlderThan(duration: Duration, block: (File) -> Unit) =
67+
inline fun File.isOlderThan(duration: Duration, block: (File) -> Unit) =
6868
ifExists { if (duration.inWholeMilliseconds < System.currentTimeMillis() - lastModified()) block(this) }
6969

7070
/**
@@ -89,7 +89,7 @@ object FileUtils {
8989
*
9090
* @param block Lambda executed if the file doesn't exist or the file is empty
9191
*/
92-
inline fun File.createIfNotExists(block: (File) -> Unit): File {
92+
inline fun File.createIfNotExists(block: (File) -> Unit = {}): File {
9393
if (length() == 0L) {
9494
parentFile.mkdirs()
9595
createNewFile()

0 commit comments

Comments
 (0)