aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorAaron <51387595+AzureAaron@users.noreply.github.com>2024-02-27 15:44:55 -0500
committerAaron <51387595+AzureAaron@users.noreply.github.com>2024-02-27 15:45:09 -0500
commita087eb62b5a79e3f5b05676ce9cbd70c89c8ba9e (patch)
tree8010f762f883a10cd9c8e283e9332ca579b2f3c4 /src/main
parent6f16df3f8049e27fa0d52a335c152d47aaf10428 (diff)
downloadSkyblocker-a087eb62b5a79e3f5b05676ce9cbd70c89c8ba9e.tar.gz
Skyblocker-a087eb62b5a79e3f5b05676ce9cbd70c89c8ba9e.tar.bz2
Skyblocker-a087eb62b5a79e3f5b05676ce9cbd70c89c8ba9e.zip
Image Repo Loader
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/config/ConfigUtils.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java144
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/FileUtils.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Http.java24
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java20
6 files changed, 225 insertions, 23 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 0bd3c8e3..2987f493 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -2,6 +2,8 @@ package de.hysky.skyblocker;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+
+import de.hysky.skyblocker.config.ImageRepoLoader;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.skyblock.*;
@@ -94,6 +96,7 @@ public class SkyblockerMod implements ClientModInitializer {
SkyblockerConfigManager.init();
Tips.init();
NEURepoManager.init();
+ ImageRepoLoader.init();
ItemRepository.init();
PlayerHeadHashCache.init();
HotbarSlotLock.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
index 8b0f27a7..781f7f15 100644
--- a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
+++ b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
@@ -1,16 +1,26 @@
package de.hysky.skyblocker.config;
import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.OptionDescription;
import dev.isxander.yacl3.api.controller.*;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.Nullable;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.FileUtils;
+import java.nio.file.Path;
import java.util.function.Function;
public class ConfigUtils {
public static final ValueFormatter<Formatting> FORMATTING_FORMATTER = formatting -> Text.literal(StringUtils.capitalize(formatting.getName().replaceAll("_", " ")));
public static final ValueFormatter<Float> FLOAT_TWO_FORMATTER = value -> Text.literal(String.format("%,.2f", value).replaceAll("[\u00a0\u202F]", " "));
+ private static final Path IMAGE_DIRECTORY = ImageRepoLoader.REPO_DIRECTORY.resolve("Skyblocker-Assets-images");
public static BooleanControllerBuilder createBooleanController(Option<Boolean> opt) {
return BooleanControllerBuilder.create(opt).yesNoFormatter().coloured(true);
@@ -34,4 +44,15 @@ public class ConfigUtils {
public static <E extends Enum<E>> Function<Option<E>, ControllerBuilder<E>> getEnumDropdownControllerFactory(ValueFormatter<E> formatter) {
return opt -> EnumDropdownControllerBuilder.create(opt).formatValue(formatter);
}
+
+ /**
+ * Creates an {@link OptionDescription} with an image and text.
+ */
+ @SafeVarargs
+ public static OptionDescription withImage(Path imagePath, @Nullable Text... texts) {
+ return OptionDescription.createBuilder()
+ .text(ArrayUtils.isNotEmpty(texts) ? texts : new Text[] {})
+ .image(IMAGE_DIRECTORY.resolve(imagePath), new Identifier(SkyblockerMod.NAMESPACE, "config_image_" + FileUtils.normalizePath(imagePath)))
+ .build();
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java b/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java
new file mode 100644
index 00000000..0591cd96
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java
@@ -0,0 +1,144 @@
+package de.hysky.skyblocker.config;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.CompletableFuture;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.slf4j.Logger;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.FileUtils;
+import de.hysky.skyblocker.utils.Http;
+
+public class ImageRepoLoader {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ static final Path REPO_DIRECTORY = SkyblockerMod.CONFIG_DIR.resolve("image-repo");
+ private static final String BRANCH_INFO = "https://api.github.com/repos/SkyblockerMod/Skyblocker-Assets/branches/images";
+ private static final String REPO_DOWNLOAD = "https://github.com/SkyblockerMod/Skyblocker-Assets/archive/refs/heads/images.zip";
+ private static final String PLACEHOLDER_HASH = "None!";
+
+ public static void init() {
+ update(0);
+ }
+
+ /**
+ * Attempts to update/load the image repository, if any errors are encountered it will try 3 times.
+ */
+ private static void update(int retries) {
+ CompletableFuture.runAsync(() -> {
+ if (retries < 3) {
+ try {
+ long start = System.currentTimeMillis();
+ //Retrieve the saved commit hash
+ String savedCommitHash = checkSavedCommitData();
+
+ //Fetch the latest commit data
+ JsonObject response = JsonParser.parseString(Http.sendGetRequest(BRANCH_INFO)).getAsJsonObject();
+ String latestCommitHash = response.getAsJsonObject("commit").get("sha").getAsString();
+
+ //Download the repository if there was a new commit
+ if (!savedCommitHash.equals(latestCommitHash)) {
+ InputStream in = Http.downloadContent(REPO_DOWNLOAD);
+
+ //Delete all directories to clear potentially now unused/old files
+ //TODO change this to only delete periodically?
+ deleteDirectories();
+
+ try (ZipInputStream zis = new ZipInputStream(in)) {
+ ZipEntry entry;
+
+ while ((entry = zis.getNextEntry()) != null) {
+ Path outputFile = REPO_DIRECTORY.resolve(entry.getName());
+
+ if (entry.isDirectory()) {
+ Files.createDirectories(outputFile);
+ } else {
+ Files.createDirectories(outputFile.getParent());
+ Files.copy(zis, outputFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+
+ writeCommitData(latestCommitHash);
+
+ long end = System.currentTimeMillis();
+ LOGGER.info("[Skyblocker] Successfully updated the Image Respository in {} ms! {} → {}", end - start, savedCommitHash, latestCommitHash);
+ } else {
+ LOGGER.info("[Skyblocker] The Image Respository is up to date!");
+ }
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Error while downloading image repo on attempt {}!", retries, e);
+ update(retries + 1);
+ }
+ }
+ });
+ }
+
+ /**
+ * @return The stored hash or the {@link #PLACEHOLDER_HASH}.
+ */
+ private static String checkSavedCommitData() throws IOException {
+ Path file = REPO_DIRECTORY.resolve("image_repo.json");
+
+ if (Files.exists(file)) {
+ try (BufferedReader reader = Files.newBufferedReader(file)) {
+ CommitData commitData = CommitData.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow();
+
+ return commitData.commit();
+ }
+ }
+
+ return PLACEHOLDER_HASH;
+ }
+
+ /**
+ * Writes the {@code newHash} into a file to be used to check for repo updates.
+ *
+ * @implNote Checking whether the directory exists or not isn't needed as this is called after all files are written successfully.
+ */
+ private static void writeCommitData(String newHash) throws IOException {
+ Path file = REPO_DIRECTORY.resolve("image_repo.json");
+ CommitData commitData = new CommitData(newHash, System.currentTimeMillis());
+
+ try (BufferedWriter writer = Files.newBufferedWriter(file)) {
+ SkyblockerMod.GSON.toJson(CommitData.CODEC.encodeStart(JsonOps.INSTANCE, commitData).result().orElseThrow(), writer);
+ }
+ }
+
+ /**
+ * Deletes all directories (not files) inside of the {@link #REPO_DIRECTORY}
+ * @throws IOException
+ */
+ private static void deleteDirectories() throws IOException {
+ Files.list(REPO_DIRECTORY)
+ .filter(Files::isDirectory)
+ .forEach(dir -> {
+ try {
+ FileUtils.recursiveDelete(dir);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an exception while deleting a directory! Path: {}", dir.toAbsolutePath(), e);
+ }
+ });
+ }
+
+ record CommitData(String commit, long lastUpdated) {
+ static final Codec<CommitData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.STRING.fieldOf("commit").forGetter(CommitData::commit),
+ Codec.LONG.fieldOf("lastUpdated").forGetter(CommitData::lastUpdated))
+ .apply(instance, CommitData::new));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/FileUtils.java b/src/main/java/de/hysky/skyblocker/utils/FileUtils.java
new file mode 100644
index 00000000..22611441
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/FileUtils.java
@@ -0,0 +1,36 @@
+package de.hysky.skyblocker.utils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.slf4j.Logger;
+
+import com.mojang.logging.LogUtils;
+
+public class FileUtils {
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ public static void recursiveDelete(Path dir) throws IOException {
+ if (Files.isDirectory(dir) && !Files.isSymbolicLink(dir)) {
+ Files.list(dir).forEach(child -> {
+ try {
+ recursiveDelete(child);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an exception while deleting a file! Path: {}", child.toAbsolutePath(), e);
+ }
+ });
+ }
+
+ Files.delete(dir);
+ }
+
+ /**
+ * Replaces any characters that do not match the regex: [^a-z0-9_.-]
+ *
+ * @implNote Designed to convert a file path to an {@link net.minecraft.util.Identifier}
+ */
+ public static String normalizePath(Path path) {
+ return path.toString().toLowerCase().replaceAll("[^a-z0-9_.-]", "");
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java
index 58deced2..871eac78 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Http.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Http.java
@@ -33,10 +33,6 @@ public class Http {
.followRedirects(Redirect.NORMAL)
.build();
- public static String sendGetRequest(String url) throws IOException, InterruptedException {
- return sendCacheableGetRequest(url).content();
- }
-
private static ApiResponse sendCacheableGetRequest(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
@@ -55,6 +51,26 @@ public class Http {
return new ApiResponse(body, response.statusCode(), getCacheStatuses(headers), getAge(headers));
}
+
+ public static InputStream downloadContent(String url) throws IOException, InterruptedException {
+ HttpRequest request = HttpRequest.newBuilder()
+ .GET()
+ .header("Accept", "*/*")
+ .header("Accept-Encoding", "gzip, deflate")
+ .header("User-Agent", USER_AGENT)
+ .version(Version.HTTP_2)
+ .uri(URI.create(url))
+ .build();
+
+ HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream());
+ InputStream decodedInputStream = getDecodedInputStream(response);
+
+ return decodedInputStream;
+ }
+
+ public static String sendGetRequest(String url) throws IOException, InterruptedException {
+ return sendCacheableGetRequest(url).content();
+ }
public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
diff --git a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
index 870e94da..c779d666 100644
--- a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
@@ -13,13 +13,10 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.File;
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
-import java.util.stream.Stream;
/**
* Initializes the NEU repo, which contains item metadata and fairy souls location data. Clones the repo if it does not exist and checks for updates. Use {@link #runAsyncAfterLoad(Runnable)} to run code after the repo is initialized.
@@ -76,7 +73,7 @@ public class NEURepoManager {
CompletableFuture.runAsync(() -> {
try {
ItemRepository.setFilesImported(false);
- recursiveDelete(NEURepoManager.LOCAL_REPO_DIR);
+ FileUtils.recursiveDelete(NEURepoManager.LOCAL_REPO_DIR);
} catch (Exception ex) {
if (MinecraftClient.getInstance().player != null)
MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.updaterepository.failed")), false);
@@ -86,21 +83,6 @@ public class NEURepoManager {
});
}
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private static void recursiveDelete(Path dir) throws IOException {
- if (Files.isDirectory(dir) && !Files.isSymbolicLink(dir)) {
- Files.list(dir).forEach(child -> {
- try {
- recursiveDelete(child);
- } catch (Exception e) {
- LOGGER.error("[Skyblocker] Encountered an exception while deleting a file! Path: {}", child.toAbsolutePath(), e);
- }
- });
- }
-
- Files.delete(dir);
- }
-
/**
* Runs the given runnable after the NEU repo is initialized.
* @param runnable the runnable to run