split up the dependency loader method into smaller parts

This commit is contained in:
FalsePattern committed 2022-07-24 23:57:03 +02:00
1 parent 1737345d43
commit 31f9719be3
1 file changed
+219 -147
@@ -8,6 +8,7 @@ import com.falsepattern.lib.internal.LibraryConfig;
import com.falsepattern.lib.internal.Tags;
import com.falsepattern.lib.util.FileUtil;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val;
import lombok.var;
@@ -48,153 +49,8 @@ public class DependencyLoaderImpl {
public static void loadLibrary(@NonNull String loadingModId, @NonNull String groupId, @NonNull String artifactId,
@NonNull Version minVersion, Version maxVersion, @NonNull Version preferredVersion,
String regularSuffix, String devSuffix) {
val suffix = FalsePatternLib.isDeveloperEnvironment() ? devSuffix : regularSuffix;
val artifactLogName =
String.format("%s:%s:%s%s", groupId, artifactId, preferredVersion, suffix != null ? "-" + suffix : "");
log.info("Adding library {}, requested by mod {}", artifactLogName, loadingModId);
var artifact = groupId + ":" + artifactId + ":" + suffix;
if (loadedLibraries.containsKey(artifact)) {
val currentVer = loadedLibraries.get(artifact);
if (currentVer.equals(preferredVersion)) {
return;
}
val rangeString = "(minimum: " + minVersion + (maxVersion == null ? "" : ", maximum: " + maxVersion) + ")";
if (minVersion.compareTo(currentVer) > 0 || (maxVersion != null && maxVersion.compareTo(currentVer) < 0)) {
for (int i = 0; i < 16; i++) {
log.fatal("ALERT VVVVVVVVVVVV ALERT");
}
log.fatal("Library {}:{}{} already loaded with version {}, " +
"but a version in the range {} was requested! Thing may go horribly wrong! " +
"Requested by mod: {}, previously loaded by mod: {}", groupId, artifactId,
suffix != null ? ":" + suffix : "", currentVer, rangeString, loadingModId,
loadedLibraryMods.get(artifact));
for (int i = 0; i < 16; i++) {
log.fatal("ALERT ^^^^^^^^^^^^ ALERT");
}
} else {
log.info("Attempted loading of library {}:{}{} with preferred version {}, " +
"but version {} was already loaded, which matched the range {}. This is not an " + "error. " +
"Requested by mod: {}, previously loaded by mod: {}", groupId, artifactId,
suffix != null ? ":" + suffix : "", preferredVersion, currentVer, rangeString, loadingModId,
loadedLibraryMods.get(artifact));
}
return;
}
String homeDir = System.getProperty("minecraft.sharedDataDir");
if (homeDir == null) {
homeDir = System.getenv("MINECRAFT_SHARED_DATA_DIR");
if (homeDir == null) {
homeDir = FileUtil.getMinecraftHome().getAbsolutePath();
}
}
val modsDir = Paths.get(homeDir, "mods").toFile();
val mavenJarName =
String.format("%s-%s%s.jar", artifactId, preferredVersion, (suffix != null) ? ("-" + suffix) : "");
val jarName = groupId + "-" + mavenJarName;
val libDir = new File(modsDir, "falsepattern");
if (!libDir.exists()) {
if (!libDir.mkdirs()) {
log.fatal("Failed to create directory {}", libDir);
throw new RuntimeException("Failed to create directory " + libDir);
}
}
val file = new File(libDir, jarName);
if (file.exists()) {
try {
addToClasspath(file);
loadedLibraries.put(artifact, preferredVersion);
log.debug("Library {} successfully loaded from disk!", artifactLogName);
return;
} catch (RuntimeException e) {
log.warn("Failed to load library {} from file! Re-downloading...", artifactLogName);
checkedDelete(file);
}
}
if (!LibraryConfig.ENABLE_LIBRARY_DOWNLOADS) {
val errorMessage = "Failed to load library " + groupId + ":" + artifactId + ":" + preferredVersion +
((suffix != null) ? ":" + suffix : "") + ": " + Tags.MODNAME +
" library downloading has been disabled in the config, and the library is not present " +
"on disk! Requested by mod: " + loadingModId;
log.fatal(errorMessage);
throw new IllegalStateException(errorMessage);
}
for (var repo : mavenRepositories) {
try {
if (!repo.endsWith("/")) {
repo = repo + "/";
}
val url = String.format("%s%s/%s/%s/%s", repo, groupId.replace('.', '/'), artifactId, preferredVersion,
mavenJarName);
String finalRepo = repo;
boolean retry = true;
int retryCount = 0;
redownload:
while (retry) {
retry = false;
retryCount++;
if (retryCount >= 3) {
break;
}
val success = new AtomicBoolean(false);
Internet.connect(new URL(url),
(ex) -> log.debug("Artifact {} could not be downloaded from repo {}: {}",
artifactLogName, finalRepo, ex.getMessage()), (input) -> {
log.debug("Downloading {} from {}", artifactLogName, finalRepo);
download(input, file);
log.debug("Downloaded {} from {}", artifactLogName, finalRepo);
success.set(true);
});
if (success.get()) {
log.debug("Validating checksum for {}", artifactLogName);
boolean hadChecksum = false;
for (val checksumType : CHECKSUM_TYPES) {
val checksumURL = url + "." + checksumType;
val checksumFile = new File(libDir, jarName + "." + checksumType);
log.debug("Attempting to get {} checksum...", checksumType);
success.set(false);
Internet.connect(new URL(checksumURL),
(ex) -> log.debug("Could not get {} checksum for {}: {}", checksumType,
artifactLogName, ex.getMessage()), (input) -> {
log.debug("Downloading {} checksum for {}", checksumType, artifactLogName);
download(input, checksumFile);
log.debug("Downloaded {} checksum for {}", checksumType, artifactLogName);
success.set(true);
});
if (success.get()) {
val fileHash = hash(checksumType, file);
val referenceHash = new String(Files.readAllBytes(checksumFile.toPath()));
if (!fileHash.equals(referenceHash)) {
log.error("Failed {} checksum validation for {}. Retrying download...",
checksumType, artifactLogName);
checkedDelete(file);
checkedDelete(checksumFile);
retry = true;
continue redownload;
}
log.debug("Successfully validated {} checksum for {}", checksumType, artifactLogName);
hadChecksum = true;
break;
}
}
if (!hadChecksum) {
log.warn("The library {} had no checksum available on the repository.\n" +
"There's a chance it might have gotten corrupted during download,\n" +
"but we're loading it anyways.", artifactLogName);
}
loadedLibraries.put(artifact, preferredVersion);
loadedLibraryMods.put(artifact, loadingModId);
addToClasspath(file);
return;
}
}
} catch (IOException ignored) {
}
}
val errorMessage = "Failed to download library " + groupId + ":" + artifactId + ":" + preferredVersion +
((suffix != null) ? ":" + suffix : "") + " from any repository! Requested by mod: " +
loadingModId;
log.fatal(errorMessage);
throw new IllegalStateException(errorMessage);
new DependencyLoadTask(loadingModId, groupId, artifactId, minVersion, maxVersion, preferredVersion, regularSuffix, devSuffix)
.load();
}
private static String bytesToHex(byte[] hash) {
@@ -258,4 +114,220 @@ public class DependencyLoaderImpl {
}
Internet.transferAndClose(is, new BufferedOutputStream(Files.newOutputStream(target.toPath())));
}
@RequiredArgsConstructor
private static class DependencyLoadTask {
@NonNull
private final String loadingModId;
@NonNull
private final String groupId;
@NonNull
private final String artifactId;
@NonNull
private final Version minVersion;
private final Version maxVersion;
@NonNull
private final Version preferredVersion;
private final String regularSuffix;
private final String devSuffix;
private String suffix;
private String artifactLogName;
private String artifact;
private String mavenJarName;
private String jarName;
private File libDir;
private File file;
private void load() {
setupLibraryNames();
if (loadedLibraries.containsKey(artifact)) {
alreadyLoaded();
return;
}
setupPaths();
if (tryLoadingExistingFile()) {
return;
}
validateDownloadsAllowed();
for (var repo : mavenRepositories) {
if (tryDownloadFromMaven(repo)) {
return;
}
}
crashCouldNotDownload();
}
private void crashCouldNotDownload() {
val errorMessage = "Failed to download library " + groupId + ":" + artifactId + ":" + preferredVersion +
((suffix != null) ? ":" + suffix : "") + " from any repository! Requested by mod: " +
loadingModId;
log.fatal(errorMessage);
throw new IllegalStateException(errorMessage);
}
private void setupLibraryNames() {
suffix = FalsePatternLib.isDeveloperEnvironment() ? devSuffix : regularSuffix;
artifactLogName =
String.format("%s:%s:%s%s", groupId, artifactId, preferredVersion, suffix != null ? "-" + suffix : "");
log.info("Adding library {}, requested by mod {}", artifactLogName, loadingModId);
artifact = groupId + ":" + artifactId + ":" + suffix;
}
private void alreadyLoaded() {
val currentVer = loadedLibraries.get(artifact);
if (currentVer.equals(preferredVersion)) {
return;
}
val rangeString = "(minimum: " + minVersion + (maxVersion == null ? "" : ", maximum: " + maxVersion) + ")";
if (minVersion.compareTo(currentVer) > 0 || (maxVersion != null && maxVersion.compareTo(currentVer) < 0)) {
for (int i = 0; i < 16; i++) {
log.fatal("ALERT VVVVVVVVVVVV ALERT");
}
log.fatal("Library {}:{}{} already loaded with version {}, " +
"but a version in the range {} was requested! Thing may go horribly wrong! " +
"Requested by mod: {}, previously loaded by mod: {}", groupId, artifactId,
suffix != null ? ":" + suffix : "", currentVer, rangeString, loadingModId,
loadedLibraryMods.get(artifact));
for (int i = 0; i < 16; i++) {
log.fatal("ALERT ^^^^^^^^^^^^ ALERT");
}
} else {
log.info("Attempted loading of library {}:{}{} with preferred version {}, " +
"but version {} was already loaded, which matched the range {}. This is not an " + "error. " +
"Requested by mod: {}, previously loaded by mod: {}", groupId, artifactId,
suffix != null ? ":" + suffix : "", preferredVersion, currentVer, rangeString, loadingModId,
loadedLibraryMods.get(artifact));
}
}
private void setupPaths() {
var homeDir = System.getProperty("minecraft.sharedDataDir");
if (homeDir == null) {
homeDir = System.getenv("MINECRAFT_SHARED_DATA_DIR");
if (homeDir == null) {
homeDir = FileUtil.getMinecraftHome().getAbsolutePath();
}
}
val modsDir = Paths.get(homeDir, "mods").toFile();
mavenJarName = String.format("%s-%s%s.jar", artifactId, preferredVersion,
(suffix != null) ? ("-" + suffix) : "");
jarName = groupId + "-" + mavenJarName;
libDir = new File(modsDir, "falsepattern");
if (!libDir.exists()) {
if (!libDir.mkdirs()) {
log.fatal("Failed to create directory {}", libDir);
throw new RuntimeException("Failed to create directory " + libDir);
}
}
file = new File(libDir, jarName);
}
private boolean tryLoadingExistingFile() {
if (!file.exists()) return false;
try {
addToClasspath(file);
loadedLibraries.put(artifact, preferredVersion);
log.debug("Library {} successfully loaded from disk!", artifactLogName);
return true;
} catch (RuntimeException e) {
log.warn("Failed to load library {} from file! Re-downloading...", artifactLogName);
checkedDelete(file);
return false;
}
}
private void validateDownloadsAllowed() {
if (!LibraryConfig.ENABLE_LIBRARY_DOWNLOADS) {
val errorMessage = "Failed to load library " + groupId + ":" + artifactId + ":" + preferredVersion +
((suffix != null) ? ":" + suffix : "") + ": " + Tags.MODNAME +
" library downloading has been disabled in the config, and the library is not present " +
"on disk! Requested by mod: " + loadingModId;
log.fatal(errorMessage);
throw new IllegalStateException(errorMessage);
}
}
private boolean tryDownloadFromMaven(String repo) {
try {
if (!repo.endsWith("/")) {
repo = repo + "/";
}
val url = String.format("%s%s/%s/%s/%s", repo, groupId.replace('.', '/'), artifactId,
preferredVersion, mavenJarName);
String finalRepo = repo;
int retryCount = 0;
while (true) {
retryCount++;
if (retryCount > 3) {
break;
}
val success = new AtomicBoolean(false);
Internet.connect(new URL(url), (ex) -> log.debug("Artifact {} could not be downloaded from repo {}: {}",
artifactLogName, finalRepo, ex.getMessage()),
(input) -> {
log.debug("Downloading {} from {}", artifactLogName, finalRepo);
download(input, file);
log.debug("Downloaded {} from {}", artifactLogName, finalRepo);
success.set(true);
});
if (success.get()) {
log.debug("Validating checksum for {}", artifactLogName);
val hadChecksum = validateChecksum(url);
switch (hadChecksum) {
case FAILED:
continue;
case OK:
break;
case MISSING:
log.warn("The library {} had no checksum available on the repository.\n" +
"There's a chance it might have gotten corrupted during download,\n" +
"but we're loading it anyways.", artifactLogName);
}
loadedLibraries.put(artifact, preferredVersion);
loadedLibraryMods.put(artifact, loadingModId);
addToClasspath(file);
return true;
}
}
} catch (IOException ignored) {
}
return false;
}
private ChecksumStatus validateChecksum(String url) throws IOException {
for (val checksumType : CHECKSUM_TYPES) {
val checksumURL = url + "." + checksumType;
val checksumFile = new File(libDir, jarName + "." + checksumType);
log.debug("Attempting to get {} checksum...", checksumType);
val success = new AtomicBoolean(false);
Internet.connect(new URL(checksumURL),
(ex) -> log.debug("Could not get {} checksum for {}: {}", checksumType, artifactLogName, ex.getMessage()),
(input) -> {
log.debug("Downloading {} checksum for {}", checksumType, artifactLogName);
download(input, checksumFile);
log.debug("Downloaded {} checksum for {}", checksumType, artifactLogName);
success.set(true);
});
if (success.get()) {
val fileHash = hash(checksumType, file);
val referenceHash = new String(Files.readAllBytes(checksumFile.toPath()));
if (!fileHash.equals(referenceHash)) {
log.error("Failed {} checksum validation for {}. Retrying download...", checksumType,
artifactLogName);
checkedDelete(file);
checkedDelete(checksumFile);
return ChecksumStatus.FAILED;
}
log.debug("Successfully validated {} checksum for {}", checksumType, artifactLogName);
return ChecksumStatus.OK;
}
}
return ChecksumStatus.MISSING;
}
private enum ChecksumStatus {
OK, FAILED, MISSING
}
}
}