1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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?
if (Files.exists(REPO_DIRECTORY)) 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));
}
}
|