diff --git a/src/main/java/me/braydon/mc/model/cache/CachedPlayerName.java b/src/main/java/me/braydon/mc/model/cache/CachedPlayerName.java index 3a60e22..5f2acae 100644 --- a/src/main/java/me/braydon/mc/model/cache/CachedPlayerName.java +++ b/src/main/java/me/braydon/mc/model/cache/CachedPlayerName.java @@ -30,12 +30,13 @@ import org.springframework.data.redis.core.RedisHash; import java.util.UUID; /** + * A cache to easily lookup a + * player's UUID by their username. + * * @author Braydon */ -@AllArgsConstructor -@Getter -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@ToString +@AllArgsConstructor @Getter +@EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @RedisHash(value = "playerName", timeToLive = 60L * 60L) // 1 hour (in seconds) public final class CachedPlayerName { /** diff --git a/src/main/java/me/braydon/mc/model/cache/CachedSkinPartTexture.java b/src/main/java/me/braydon/mc/model/cache/CachedSkinPartTexture.java new file mode 100644 index 0000000..1bfe48a --- /dev/null +++ b/src/main/java/me/braydon/mc/model/cache/CachedSkinPartTexture.java @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2024 Braydon (Rainnny). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package me.braydon.mc.model.cache; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +import java.io.Serializable; + +/** + * A cache for a skin part texture. + * + * @author Braydon + */ +@AllArgsConstructor @Getter @ToString +@RedisHash(value = "skinPart", timeToLive = 15L * 60L) // 15 minutes (in seconds) +public final class CachedSkinPartTexture implements Serializable { + /** + * The id of this cache element. + *

+ * This ID is in the given format: + * skinPart:--- + *

+ */ + @Id private transient final String id; + + /** + * The cached texture; + */ + private final byte[] texture; +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/repository/SkinPartTextureCacheRepository.java b/src/main/java/me/braydon/mc/repository/SkinPartTextureCacheRepository.java new file mode 100644 index 0000000..b61d5f7 --- /dev/null +++ b/src/main/java/me/braydon/mc/repository/SkinPartTextureCacheRepository.java @@ -0,0 +1,36 @@ +/* + * MIT License + * + * Copyright (c) 2024 Braydon (Rainnny). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package me.braydon.mc.repository; + +import me.braydon.mc.model.Skin.Part; +import me.braydon.mc.model.cache.CachedSkinPartTexture; +import org.springframework.data.repository.CrudRepository; + +/** + * A cache repository for skin texture parts. + * + * @author Braydon + * @see Part for skin parts + */ +public interface SkinPartTextureCacheRepository extends CrudRepository { } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index 0e51e51..c4b35ee 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -41,12 +41,14 @@ import me.braydon.mc.model.*; import me.braydon.mc.model.cache.CachedMinecraftServer; import me.braydon.mc.model.cache.CachedPlayer; import me.braydon.mc.model.cache.CachedPlayerName; +import me.braydon.mc.model.cache.CachedSkinPartTexture; import me.braydon.mc.model.server.JavaMinecraftServer; import me.braydon.mc.model.token.MojangProfileToken; import me.braydon.mc.model.token.MojangUsernameToUUIDToken; import me.braydon.mc.repository.MinecraftServerCacheRepository; import me.braydon.mc.repository.PlayerCacheRepository; import me.braydon.mc.repository.PlayerNameCacheRepository; +import me.braydon.mc.repository.SkinPartTextureCacheRepository; import net.jodah.expiringmap.ExpirationPolicy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; @@ -91,6 +93,11 @@ public final class MojangService { */ @NonNull private final PlayerCacheRepository playerCache; + /** + * The cache repository for {@link Skin.Part}'s. + */ + @NonNull private final SkinPartTextureCacheRepository skinPartTextureCache; + /** * The cache repository for {@link MinecraftServer}'s. */ @@ -116,12 +123,16 @@ public final class MojangService { @Autowired public MojangService(@NonNull PlayerNameCacheRepository playerNameCache, @NonNull PlayerCacheRepository playerCache, - @NonNull MinecraftServerCacheRepository minecraftServerCache) { + @NonNull SkinPartTextureCacheRepository skinPartTextureCache, @NonNull MinecraftServerCacheRepository minecraftServerCache) { this.playerNameCache = playerNameCache; this.playerCache = playerCache; + this.skinPartTextureCache = skinPartTextureCache; this.minecraftServerCache = minecraftServerCache; } + @Autowired + + @PostConstruct public void onInitialize() { // Schedule a task to fetch blocked @@ -167,6 +178,12 @@ public final class MojangService { size = DEFAULT_PART_TEXTURE_SIZE; } size = Math.min(size, MAX_PART_TEXTURE_SIZE); // Limit the size to 512 + String id = "%s-%s-%s-%s".formatted(query, part.name(), size, extension); // The id of the skin part + + Optional cached = skinPartTextureCache.findById(id); // Get the cached texture + if (cached.isPresent()) { // Respond with the cache if present + return cached.get().getTexture(); + } Skin target = null; // The target skin to get the skin part of try { @@ -178,7 +195,9 @@ public final class MojangService { if (target == null) { // Fallback to the default skin target = Skin.DEFAULT_STEVE; } - return ImageUtils.getSkinPart(target, part, size); + byte[] texture = ImageUtils.getSkinPart(target, part, size); + skinPartTextureCache.save(new CachedSkinPartTexture(id, texture)); // Cache the texture + return texture; } /** @@ -369,7 +388,7 @@ public final class MojangService { } // Check the cache for the server - Optional cached = minecraftServerCache.findById(platform.name() + "-" + lookupHostname); + Optional cached = minecraftServerCache.findById("%s-%s".formatted(platform.name(), lookupHostname)); if (cached.isPresent()) { // Respond with the cache if present log.info("Found server in cache: {}", hostname); return cached.get();