Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9969dc60c5 | ||
|
|
6230b436ad | ||
|
|
3873459aaa | ||
|
|
4ee3857427 | ||
|
|
2617dd3630 | ||
|
|
e0425102c8 | ||
|
|
a470475c21 | ||
|
|
bcd73e6d15 | ||
|
|
7d6f21dc3a | ||
|
|
3eddfa23b7 | ||
|
|
d5fa232704 | ||
|
|
c84afb3155 | ||
|
|
401fef5774 | ||
|
|
9298fe48e1 | ||
|
|
7f51e0395c | ||
|
|
4f410fb8ad | ||
|
|
9bcf95287a | ||
|
|
5703377626 | ||
|
|
3adb3a67e9 | ||
|
|
7918b333e6 | ||
|
|
329b0e8f3a | ||
|
|
3b17e0a897 | ||
|
|
e10f1dac20 | ||
|
|
86c171545e | ||
|
|
c3a3881cfc | ||
|
|
7f04cdd6b1 |
No files matched your search
@@ -15,11 +15,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Check Codes
|
||||
run: ./gradlew checkstyleMain checkstyleTest checkTranslations --no-daemon --parallel
|
||||
run: ./gradlew checkstyle checkTranslations --no-daemon --parallel
|
||||
@@ -13,11 +13,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon
|
||||
|
||||
@@ -47,6 +47,21 @@ public final class EntryPoint {
|
||||
createHMCLDirectories();
|
||||
LOG.start(Metadata.HMCL_CURRENT_DIRECTORY.resolve("logs"));
|
||||
|
||||
if ("true".equalsIgnoreCase(System.getenv("HMCL_FORCE_GPU")))
|
||||
System.getProperties().putIfAbsent("prism.forceGPU", "true");
|
||||
|
||||
String animationFrameRate = System.getenv("HMCL_ANIMATION_FRAME_RATE");
|
||||
if (animationFrameRate != null) {
|
||||
try {
|
||||
if (Integer.parseInt(animationFrameRate) <= 0)
|
||||
throw new NumberFormatException(animationFrameRate);
|
||||
|
||||
System.getProperties().putIfAbsent("javafx.animation.pulse", animationFrameRate);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warning("Invalid animation frame rate: " + animationFrameRate);
|
||||
}
|
||||
}
|
||||
|
||||
checkDirectoryPath();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.ButtonType;
|
||||
@@ -27,6 +28,7 @@ import javafx.scene.input.DataFormat;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.SambaException;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.task.AsyncTaskExecutor;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -54,6 +56,8 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
@@ -78,6 +82,9 @@ public final class Launcher extends Application {
|
||||
LOG.warning("Failed to get prism pipeline", e);
|
||||
}
|
||||
|
||||
LOG.info("Dark Mode: " + Optional.ofNullable(FXUtils.DARK_MODE).map(ObservableBooleanValue::get).orElse(false));
|
||||
LOG.info("Reduced Motion: " + Objects.requireNonNullElse(FXUtils.REDUCED_MOTION, false));
|
||||
|
||||
try {
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
|
||||
@@ -158,6 +158,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
blackList.add("saves");
|
||||
|
||||
if (Files.exists(dstDir)) throw new IOException("Version exists");
|
||||
|
||||
Files.createDirectories(dstDir);
|
||||
FileUtils.copyDirectory(srcDir, dstDir, path -> Modpack.acceptFile(path, blackList, null));
|
||||
|
||||
Path fromJson = srcDir.resolve(srcId + ".json");
|
||||
@@ -170,7 +172,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
Files.copy(fromJson, toJson);
|
||||
|
||||
FileUtils.writeText(toJson, JsonUtils.GSON.toJson(fromVersion.setId(dstId)));
|
||||
JsonUtils.writeToJsonFile(toJson, fromVersion.setId(dstId));
|
||||
|
||||
VersionSetting oldVersionSetting = getVersionSetting(srcId).clone();
|
||||
GameDirectoryType originalGameDirType = oldVersionSetting.getGameDirType();
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -66,7 +65,7 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
ModpackConfiguration<Modpack> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(Files.readString(json.toPath()), ModpackConfiguration.typeOf(Modpack.class));
|
||||
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(Modpack.class));
|
||||
|
||||
if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version.");
|
||||
|
||||
@@ -53,7 +53,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
@@ -267,27 +267,27 @@ public final class LauncherHelper {
|
||||
if (ex.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
URI uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", url);
|
||||
message += i18n("download.code.404", uri);
|
||||
else
|
||||
message += i18n("download.failed", url, responseCode);
|
||||
message += i18n("download.failed", uri, responseCode);
|
||||
} else {
|
||||
message += StringUtils.getStackTrace(ex.getCause());
|
||||
}
|
||||
} else if (ex instanceof DownloadException) {
|
||||
URL url = ((DownloadException) ex).getUrl();
|
||||
URI uri = ((DownloadException) ex).getUri();
|
||||
if (ex.getCause() instanceof SocketTimeoutException) {
|
||||
message = i18n("install.failed.downloading.timeout", url);
|
||||
message = i18n("install.failed.downloading.timeout", uri);
|
||||
} else if (ex.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException responseCodeException = (ResponseCodeException) ex.getCause();
|
||||
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||
message = i18n("download.code." + responseCodeException.getResponseCode(), url);
|
||||
message = i18n("download.code." + responseCodeException.getResponseCode(), uri);
|
||||
} else {
|
||||
message = i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
message = i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
}
|
||||
} else {
|
||||
message = i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
message = i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
}
|
||||
} else if (ex instanceof GameAssetIndexDownloadTask.GameAssetIndexMalformedException) {
|
||||
message = i18n("assets.index.malformed");
|
||||
@@ -298,11 +298,11 @@ public final class LauncherHelper {
|
||||
} else if (ex instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex;
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
URI uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message = i18n("download.code.404", url);
|
||||
message = i18n("download.code.404", uri);
|
||||
else
|
||||
message = i18n("download.failed", url, responseCode);
|
||||
message = i18n("download.failed", uri, responseCode);
|
||||
} else if (ex instanceof CommandTooLongException) {
|
||||
message = i18n("launch.failed.command_too_long");
|
||||
} else if (ex instanceof ExecutionPolicyLimitException) {
|
||||
|
||||
@@ -140,7 +140,7 @@ public final class ModpackHelper {
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
else
|
||||
try {
|
||||
return JsonUtils.GSON.fromJson(Files.readString(file.toPath()), ModpackConfiguration.class);
|
||||
return JsonUtils.fromJsonFile(file.toPath(), ModpackConfiguration.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -110,7 +110,7 @@ public final class TexturesLoader {
|
||||
if (!Files.isRegularFile(file)) {
|
||||
// download it
|
||||
try {
|
||||
new FileDownloadTask(new URL(texture.getUrl()), file.toFile()).run();
|
||||
new FileDownloadTask(URI.create(texture.getUrl()), file).run();
|
||||
LOG.info("Texture downloaded: " + texture.getUrl());
|
||||
} catch (Exception e) {
|
||||
if (Files.isRegularFile(file)) {
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
@@ -116,11 +115,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
}
|
||||
|
||||
if (Files.isDirectory(javaDir)) {
|
||||
JavaManifest manifest;
|
||||
try (InputStream input = Files.newInputStream(file)) {
|
||||
manifest = JsonUtils.fromJsonFully(input, JavaManifest.class);
|
||||
}
|
||||
|
||||
JavaManifest manifest = JsonUtils.fromJsonFile(file, JavaManifest.class);
|
||||
list.add(JavaRuntime.of(executable, manifest.getInfo(), isManaged));
|
||||
}
|
||||
}
|
||||
@@ -191,7 +186,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
});
|
||||
|
||||
JavaManifest manifest = new JavaManifest(info, update, files);
|
||||
FileUtils.writeText(getManifestFile(platform, gameJavaVersion), JsonUtils.GSON.toJson(manifest));
|
||||
JsonUtils.writeToJsonFile(getManifestFile(platform, gameJavaVersion), manifest);
|
||||
return JavaRuntime.of(executable, info, true);
|
||||
});
|
||||
}
|
||||
@@ -203,7 +198,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
throw new IOException("Platform is mismatch: expected " + platform + " but got " + result.getInfo().getPlatform());
|
||||
|
||||
Path executable = javaDir.resolve("bin").resolve(platform.getOperatingSystem().getJavaExecutable()).toRealPath();
|
||||
FileUtils.writeText(getManifestFile(platform, name), JsonUtils.GSON.toJson(result));
|
||||
JsonUtils.writeToJsonFile(getManifestFile(platform, name), result);
|
||||
return JavaRuntime.of(executable, result.getInfo(), true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -411,8 +411,15 @@ public final class JavaManager {
|
||||
if (System.getenv("PATH") != null) {
|
||||
String[] paths = System.getenv("PATH").split(File.pathSeparator);
|
||||
for (String path : paths) {
|
||||
// https://github.com/HMCL-dev/HMCL/issues/4079
|
||||
// https://github.com/Meloong-Git/PCL/issues/4261
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && path.toLowerCase(Locale.ROOT)
|
||||
.contains("\\common files\\oracle\\java\\")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get(path, OperatingSystem.CURRENT_OS.getJavaExecutable()));
|
||||
tryAddJavaExecutable(javaRuntimes, Path.of(path, OperatingSystem.CURRENT_OS.getJavaExecutable()));
|
||||
} catch (InvalidPathException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,7 @@ public final class AuthlibInjectorServers implements Validation {
|
||||
if (ConfigHolder.isNewlyCreated() && Files.exists(configLocation)) {
|
||||
AuthlibInjectorServers configInstance;
|
||||
try {
|
||||
String content = Files.readString(configLocation);
|
||||
configInstance = JsonUtils.GSON.fromJson(content, AuthlibInjectorServers.class);
|
||||
configInstance = JsonUtils.fromJsonFile(configLocation, AuthlibInjectorServers.class);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.warning("Malformed authlib-injectors.json", e);
|
||||
return;
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
@@ -137,26 +137,26 @@ public final class DownloadProviders {
|
||||
|
||||
public static String localizeErrorMessage(Throwable exception) {
|
||||
if (exception instanceof DownloadException) {
|
||||
URL url = ((DownloadException) exception).getUrl();
|
||||
URI uri = ((DownloadException) exception).getUri();
|
||||
if (exception.getCause() instanceof SocketTimeoutException) {
|
||||
return i18n("install.failed.downloading.timeout", url);
|
||||
return i18n("install.failed.downloading.timeout", uri);
|
||||
} else if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();
|
||||
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||
return i18n("download.code." + responseCodeException.getResponseCode(), url);
|
||||
return i18n("download.code." + responseCodeException.getResponseCode(), uri);
|
||||
} else {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
} else if (exception.getCause() instanceof FileNotFoundException) {
|
||||
return i18n("download.code.404", url);
|
||||
return i18n("download.code.404", uri);
|
||||
} else if (exception.getCause() instanceof AccessDeniedException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.access_denied", ((AccessDeniedException) exception.getCause()).getFile());
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.access_denied", ((AccessDeniedException) exception.getCause()).getFile());
|
||||
} else if (exception.getCause() instanceof ArtifactMalformedException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.artifact_malformed");
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.artifact_malformed");
|
||||
} else if (exception.getCause() instanceof SSLHandshakeException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.ssl_handshake");
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.ssl_handshake");
|
||||
} else {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
} else if (exception instanceof ArtifactMalformedException) {
|
||||
return i18n("exception.artifact_malformed");
|
||||
|
||||
@@ -18,13 +18,15 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -32,7 +34,10 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
public final class ProxyManager {
|
||||
|
||||
private static final ProxySelector NO_PROXY = new SimpleProxySelector(Proxy.NO_PROXY);
|
||||
private static final ProxySelector SYSTEM_DEFAULT = Lang.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY);
|
||||
private static final ProxySelector SYSTEM_DEFAULT = Objects.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY);
|
||||
|
||||
private static volatile @NotNull ProxySelector defaultProxySelector = SYSTEM_DEFAULT;
|
||||
private static volatile @Nullable SimpleAuthenticator defaultAuthenticator = null;
|
||||
|
||||
private static ProxySelector getProxySelector() {
|
||||
if (config().hasProxy()) {
|
||||
@@ -53,7 +58,7 @@ public final class ProxyManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static Authenticator getAuthenticator() {
|
||||
private static SimpleAuthenticator getAuthenticator() {
|
||||
if (config().hasProxy() && config().hasProxyAuth()) {
|
||||
String username = config().getProxyUser();
|
||||
String password = config().getProxyPass();
|
||||
@@ -67,15 +72,34 @@ public final class ProxyManager {
|
||||
}
|
||||
|
||||
static void init() {
|
||||
ProxySelector.setDefault(getProxySelector());
|
||||
InvalidationListener updateProxySelector = observable -> ProxySelector.setDefault(getProxySelector());
|
||||
ProxySelector.setDefault(new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
return defaultProxySelector.select(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
|
||||
defaultProxySelector.connectFailed(uri, sa, ioe);
|
||||
}
|
||||
});
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
var defaultAuthenticator = ProxyManager.defaultAuthenticator;
|
||||
return defaultAuthenticator != null ? defaultAuthenticator.getPasswordAuthentication() : null;
|
||||
}
|
||||
});
|
||||
|
||||
defaultProxySelector = getProxySelector();
|
||||
InvalidationListener updateProxySelector = observable -> defaultProxySelector = getProxySelector();
|
||||
config().proxyTypeProperty().addListener(updateProxySelector);
|
||||
config().proxyHostProperty().addListener(updateProxySelector);
|
||||
config().proxyPortProperty().addListener(updateProxySelector);
|
||||
config().hasProxyProperty().addListener(updateProxySelector);
|
||||
|
||||
Authenticator.setDefault(getAuthenticator());
|
||||
InvalidationListener updateAuthenticator = observable -> Authenticator.setDefault(getAuthenticator());
|
||||
defaultAuthenticator = getAuthenticator();
|
||||
InvalidationListener updateAuthenticator = observable -> defaultAuthenticator = getAuthenticator();
|
||||
config().hasProxyProperty().addListener(updateAuthenticator);
|
||||
config().hasProxyAuthProperty().addListener(updateAuthenticator);
|
||||
config().proxyUserProperty().addListener(updateAuthenticator);
|
||||
@@ -124,7 +148,7 @@ public final class ProxyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
return getRequestorType() == RequestorType.PROXY ? new PasswordAuthentication(username, password) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,13 @@ import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.WeakListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.*;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventType;
|
||||
@@ -41,10 +45,7 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
@@ -54,9 +55,8 @@ import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.glavo.png.PNGType;
|
||||
import org.glavo.png.PNGWriter;
|
||||
import org.glavo.png.javafx.PNGJavaFXUtils;
|
||||
import org.jackhuang.hmcl.task.CacheFileTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
@@ -66,6 +66,7 @@ import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -78,11 +79,18 @@ import javax.imageio.stream.ImageInputStream;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -105,6 +113,11 @@ public final class FXUtils {
|
||||
|
||||
public static final int JAVAFX_MAJOR_VERSION;
|
||||
|
||||
/// @see Platform.Preferences
|
||||
public static final @Nullable ObservableMap<String, Object> PREFERENCES;
|
||||
public static final @Nullable ObservableBooleanValue DARK_MODE;
|
||||
public static final @Nullable Boolean REDUCED_MOTION;
|
||||
|
||||
static {
|
||||
String jfxVersion = System.getProperty("javafx.version");
|
||||
int majorVersion = -1;
|
||||
@@ -115,6 +128,40 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
JAVAFX_MAJOR_VERSION = majorVersion;
|
||||
|
||||
ObservableMap<String, Object> preferences = null;
|
||||
ObservableBooleanValue darkMode = null;
|
||||
Boolean reducedMotion = null;
|
||||
if (JAVAFX_MAJOR_VERSION >= 22) {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
Class<?> preferencesClass = Class.forName("javafx.application.Platform$Preferences");
|
||||
@SuppressWarnings("unchecked")
|
||||
var preferences0 = (ObservableMap<String, Object>) lookup.findStatic(Platform.class, "getPreferences", MethodType.methodType(preferencesClass))
|
||||
.invoke();
|
||||
preferences = preferences0;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var colorSchemeProperty =
|
||||
(ReadOnlyObjectProperty<? extends Enum<?>>)
|
||||
lookup.findVirtual(preferencesClass, "colorSchemeProperty", MethodType.methodType(ReadOnlyObjectProperty.class))
|
||||
.invoke(preferences);
|
||||
|
||||
darkMode = Bindings.createBooleanBinding(() ->
|
||||
"DARK".equals(colorSchemeProperty.get().name()), colorSchemeProperty);
|
||||
|
||||
if (JAVAFX_MAJOR_VERSION >= 24) {
|
||||
reducedMotion = (boolean)
|
||||
lookup.findVirtual(preferencesClass, "isReducedMotion", MethodType.methodType(boolean.class))
|
||||
.invoke(preferences);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get preferences", e);
|
||||
}
|
||||
}
|
||||
PREFERENCES = preferences;
|
||||
DARK_MODE = darkMode;
|
||||
REDUCED_MOTION = reducedMotion;
|
||||
}
|
||||
|
||||
public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
|
||||
@@ -124,18 +171,8 @@ public final class FXUtils {
|
||||
);
|
||||
|
||||
private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Path> remoteImageCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static void shutdown() {
|
||||
for (Map.Entry<String, Path> entry : remoteImageCache.entrySet()) {
|
||||
try {
|
||||
Files.deleteIfExists(entry.getValue());
|
||||
} catch (IOException e) {
|
||||
LOG.warning(String.format("Failed to delete cache file %s.", entry.getValue()), e);
|
||||
}
|
||||
remoteImageCache.remove(entry.getKey());
|
||||
}
|
||||
|
||||
builtinImageCache.clear();
|
||||
}
|
||||
|
||||
@@ -799,14 +836,14 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(URL url) throws Exception {
|
||||
URLConnection connection = NetworkUtils.createConnection(url);
|
||||
public static Image loadImage(URI uri) throws Exception {
|
||||
URLConnection connection = NetworkUtils.createConnection(uri);
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
String path = url.getPath();
|
||||
String path = uri.getPath();
|
||||
if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.')))
|
||||
return loadWebPImage(input);
|
||||
else {
|
||||
@@ -858,78 +895,55 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load image from the internet. It will cache the data of images for the further usage.
|
||||
* The cached data will be deleted when HMCL is closed or hidden.
|
||||
*
|
||||
* @param url the url of image. The image resource should be a file on the internet.
|
||||
* @return the image resource within the jar.
|
||||
*/
|
||||
public static Image newRemoteImage(String url) {
|
||||
return newRemoteImage(url, 0, 0, false, false, false);
|
||||
public static Task<Image> getRemoteImageTask(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
return new CacheFileTask(URI.create(url))
|
||||
.thenApplyAsync(file -> {
|
||||
try (var channel = FileChannel.open(file, StandardOpenOption.READ)) {
|
||||
var header = new byte[12];
|
||||
var buffer = ByteBuffer.wrap(header);
|
||||
|
||||
//noinspection StatementWithEmptyBody
|
||||
while (channel.read(buffer) > 0) {
|
||||
}
|
||||
|
||||
channel.position(0L);
|
||||
if (!buffer.hasRemaining()) {
|
||||
// WebP File
|
||||
if (header[0] == 'R' && header[1] == 'I' && header[2] == 'F' && header[3] == 'F' &&
|
||||
header[8] == 'W' && header[9] == 'E' && header[10] == 'B' && header[11] == 'P') {
|
||||
|
||||
WebPImageReaderSpi spi = new WebPImageReaderSpi();
|
||||
ImageReader reader = spi.createReaderInstance(null);
|
||||
BufferedImage bufferedImage;
|
||||
try (ImageInputStream imageInput = ImageIO.createImageInputStream(Channels.newInputStream(channel))) {
|
||||
reader.setInput(imageInput, true, true);
|
||||
bufferedImage = reader.read(0, reader.getDefaultReadParam());
|
||||
} finally {
|
||||
reader.dispose();
|
||||
}
|
||||
return SwingFXUtils.toFXImage(bufferedImage, requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||
}
|
||||
}
|
||||
|
||||
Image image = new Image(Channels.newInputStream(channel), requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||
if (image.isError())
|
||||
throw image.getException();
|
||||
return image;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load image from the internet. It will cache the data of images for the further usage.
|
||||
* The cached data will be deleted when HMCL is closed or hidden.
|
||||
*
|
||||
* @param url the url of image. The image resource should be a file on the internet.
|
||||
* @param requestedWidth the image's bounding box width
|
||||
* @param requestedHeight the image's bounding box height
|
||||
* @param preserveRatio indicates whether to preserve the aspect ratio of
|
||||
* the original image when scaling to fit the image within the
|
||||
* specified bounding box
|
||||
* @param smooth indicates whether to use a better quality filtering
|
||||
* algorithm or a faster one when scaling this image to fit within
|
||||
* the specified bounding box
|
||||
* @return the image resource within the jar.
|
||||
*/
|
||||
public static Image newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth, boolean backgroundLoading) {
|
||||
Path currentPath = remoteImageCache.get(url);
|
||||
if (currentPath != null) {
|
||||
if (Files.isReadable(currentPath)) {
|
||||
try (InputStream inputStream = Files.newInputStream(currentPath)) {
|
||||
return new Image(inputStream, requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("An exception encountered while reading data from cached image file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// The file is unavailable or unreadable.
|
||||
remoteImageCache.remove(url);
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(currentPath);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("An exception encountered while deleting broken cached image file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
Image image = new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth, backgroundLoading);
|
||||
image.progressProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue.doubleValue() >= 1.0 && !image.isError() && image.getPixelReader() != null && image.getWidth() > 0.0 && image.getHeight() > 0.0) {
|
||||
Task.runAsync(() -> {
|
||||
Path newPath = Files.createTempFile("hmcl-net-resource-cache-", ".cache");
|
||||
try ( // Make sure the file is released from JVM before we put the path into remoteImageCache.
|
||||
OutputStream outputStream = Files.newOutputStream(newPath);
|
||||
PNGWriter writer = new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL)
|
||||
) {
|
||||
writer.write(PNGJavaFXUtils.asArgbImage(image));
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
Files.delete(newPath);
|
||||
} catch (IOException e2) {
|
||||
e2.addSuppressed(e);
|
||||
throw e2;
|
||||
}
|
||||
throw e;
|
||||
public static ObservableValue<Image> newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
var image = new SimpleObjectProperty<Image>();
|
||||
getRemoteImageTask(url, requestedWidth, requestedHeight, preserveRatio, smooth)
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
image.set(result);
|
||||
} else {
|
||||
LOG.warning("An exception encountered while loading remote image: " + url, exception);
|
||||
}
|
||||
if (remoteImageCache.putIfAbsent(url, newPath) != null) {
|
||||
Files.delete(newPath); // The image has been loaded in another task. Delete the image here in order not to pollute the tmp folder.
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
})
|
||||
.start();
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
@@ -190,10 +190,12 @@ public final class HTMLRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
Image image = FXUtils.newRemoteImage(uri.toString(), width, height, true, true, false);
|
||||
if (image.isError()) {
|
||||
LOG.warning("Failed to load image: " + uri, image.getException());
|
||||
} else {
|
||||
try {
|
||||
Image image = FXUtils.getRemoteImageTask(uri.toString(), width, height, true, true)
|
||||
.run();
|
||||
if (image == null)
|
||||
throw new AssertionError("Image loading task returned null");
|
||||
|
||||
ImageView imageView = new ImageView(image);
|
||||
if (hyperlink != null) {
|
||||
URI target = resolveLink(hyperlink);
|
||||
@@ -204,6 +206,8 @@ public final class HTMLRenderer {
|
||||
}
|
||||
children.add(imageView);
|
||||
return;
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to load image: " + uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.game.Log;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
@@ -186,7 +185,7 @@ public final class LogWindow extends Stage {
|
||||
}
|
||||
|
||||
private void onTerminateGame() {
|
||||
LauncherHelper.stopManagedProcesses();
|
||||
LogWindow.this.gameProcess.stop();
|
||||
}
|
||||
|
||||
private void onClear() {
|
||||
|
||||
@@ -33,6 +33,7 @@ import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -128,6 +129,10 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
SpinnerPane spinnerRefresh = new SpinnerPane();
|
||||
spinnerRefresh.getStyleClass().setAll("small-spinner-pane");
|
||||
if (skinnable.getAccount() instanceof MicrosoftAccount && Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
|
||||
btnRefresh.setDisable(true);
|
||||
FXUtils.installFastTooltip(spinnerRefresh, i18n("account.methods.microsoft.snapshot"));
|
||||
}
|
||||
btnRefresh.setOnAction(e -> {
|
||||
spinnerRefresh.showSpinner();
|
||||
skinnable.refreshAsync()
|
||||
|
||||
@@ -31,10 +31,10 @@ import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
@@ -196,7 +196,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||
lblServerWarning.setVisible("http".equals(URI.create(serverBeingAdded.getUrl()).getScheme()));
|
||||
|
||||
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT);
|
||||
} else {
|
||||
|
||||
@@ -267,7 +267,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}).executor(true);
|
||||
};
|
||||
|
||||
if (factory instanceof OfflineAccountFactory && username != null && !USERNAME_CHECKER_PATTERN.matcher(username).matches()) {
|
||||
if (factory instanceof OfflineAccountFactory && username != null && (!USERNAME_CHECKER_PATTERN.matcher(username).matches() || username.length() > 16)) {
|
||||
JFXButton btnYes = new JFXButton(i18n("button.ok"));
|
||||
btnYes.getStyleClass().add("dialog-error");
|
||||
btnYes.setOnAction(e -> doCreate.run());
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
/**
|
||||
@@ -36,7 +37,7 @@ public final class AnimationUtils {
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled() && FXUtils.REDUCED_MOTION != Boolean.TRUE;
|
||||
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED && !OperatingSystem.CURRENT_OS.isLinuxOrBSD();
|
||||
|
||||
public static boolean isAnimationEnabled() {
|
||||
|
||||
@@ -26,9 +26,7 @@ import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -43,7 +41,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public class TaskExecutorDialogPane extends BorderPane {
|
||||
private TaskExecutor executor;
|
||||
private TaskCancellationAction onCancel;
|
||||
private final Consumer<FileDownloadTask.SpeedEvent> speedEventHandler;
|
||||
private final Consumer<FetchTask.SpeedEvent> speedEventHandler;
|
||||
|
||||
private final Label lblTitle;
|
||||
private final Label lblProgress;
|
||||
@@ -108,7 +106,7 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
String finalUnit = unit;
|
||||
Platform.runLater(() -> lblProgress.setText(String.format("%.1f %s", finalSpeed, finalUnit)));
|
||||
};
|
||||
FileDownloadTask.speedEvent.channel(FileDownloadTask.SpeedEvent.class).registerWeak(speedEventHandler);
|
||||
FileDownloadTask.speedEvent.channel(FetchTask.SpeedEvent.class).registerWeak(speedEventHandler);
|
||||
|
||||
onEscPressed(this, btnCancel::fire);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -214,7 +214,7 @@ public class DecoratorController {
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
if (backgroundImageUrl != null) {
|
||||
try {
|
||||
image = FXUtils.loadImage(new URL(backgroundImageUrl));
|
||||
image = FXUtils.loadImage(URI.create(backgroundImageUrl));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
@@ -156,7 +156,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
Controllers.taskDialog(Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
|
||||
var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest);
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
|
||||
@@ -29,9 +29,7 @@ import javafx.scene.shape.SVGPath;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -43,7 +41,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
@@ -128,7 +126,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
private void onChooseRemoteFile() {
|
||||
Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (urlString, resolve, reject) -> {
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
URI url = URI.create(urlString);
|
||||
if (urlString.endsWith("server-manifest.json")) {
|
||||
// if urlString ends with .json, we assume that the url is server-manifest.json
|
||||
Controllers.taskDialog(new GetTask(url).whenComplete(Schedulers.javafx(), (result, e) -> {
|
||||
@@ -150,7 +148,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
resolve.run();
|
||||
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(url, modpack.toFile(), null)
|
||||
new FileDownloadTask(url, modpack, null)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
resolve.run();
|
||||
|
||||
+9
-9
@@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -133,28 +133,28 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) exception.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
URI uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", url);
|
||||
message += i18n("download.code.404", uri);
|
||||
else
|
||||
message += i18n("download.failed", url, responseCode);
|
||||
message += i18n("download.failed", uri, responseCode);
|
||||
} else {
|
||||
message += StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
Controllers.dialog(message, i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
} else if (exception instanceof DownloadException) {
|
||||
URL url = ((DownloadException) exception).getUrl();
|
||||
URI uri = ((DownloadException) exception).getUri();
|
||||
if (exception.getCause() instanceof SocketTimeoutException) {
|
||||
Controllers.dialog(i18n("install.failed.downloading.timeout", url), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("install.failed.downloading.timeout", uri), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
} else if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();
|
||||
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||
Controllers.dialog(i18n("download.code." + responseCodeException.getResponseCode(), url), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("download.code." + responseCodeException.getResponseCode(), uri), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
}
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
}
|
||||
} else if (exception instanceof UnsupportedInstallationException) {
|
||||
switch (((UnsupportedInstallationException) exception).getReason()) {
|
||||
|
||||
@@ -49,6 +49,8 @@ import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
@@ -67,7 +69,6 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -96,7 +97,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
private final StackPane center;
|
||||
|
||||
private final VersionList<?> versionList;
|
||||
private CompletableFuture<?> executor;
|
||||
private Task<?> executor;
|
||||
|
||||
private final HBox searchBar;
|
||||
private final StringProperty queryString = new SimpleStringProperty();
|
||||
@@ -308,7 +309,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
public void refresh() {
|
||||
VersionList<?> currentVersionList = versionList;
|
||||
root.setContent(spinner, ContainerAnimations.FADE);
|
||||
executor = currentVersionList.refreshAsync(gameVersion).whenComplete((result, exception) -> {
|
||||
executor = currentVersionList.refreshAsync(gameVersion).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
List<RemoteVersion> items = loadVersions();
|
||||
|
||||
@@ -338,6 +339,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
// https://github.com/HMCL-dev/HMCL/issues/938
|
||||
System.gc();
|
||||
});
|
||||
executor.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -348,8 +350,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(libraryId);
|
||||
if (executor != null)
|
||||
executor.cancel(true);
|
||||
// fixme
|
||||
// if (executor != null)
|
||||
// executor.cancel(true);
|
||||
}
|
||||
|
||||
private void onRefresh() {
|
||||
|
||||
@@ -34,9 +34,6 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -117,8 +114,8 @@ public final class AboutPage extends StackPane {
|
||||
return componentList;
|
||||
}
|
||||
|
||||
try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) {
|
||||
JsonArray array = JsonUtils.GSON.fromJson(reader, JsonArray.class);
|
||||
try {
|
||||
JsonArray array = JsonUtils.fromJsonFully(input, JsonArray.class);
|
||||
|
||||
for (JsonElement element : array) {
|
||||
JsonObject obj = element.getAsJsonObject();
|
||||
|
||||
@@ -124,6 +124,7 @@ public class DownloadSettingsPage extends StackPane {
|
||||
|
||||
{
|
||||
HBox hbox = new HBox(8);
|
||||
hbox.setStyle("-fx-view-order: -1;"); // prevent the indicator from being covered by the hint
|
||||
hbox.setAlignment(Pos.CENTER);
|
||||
hbox.setPadding(new Insets(0, 0, 0, 30));
|
||||
hbox.disableProperty().bind(config().autoDownloadThreadsProperty());
|
||||
|
||||
@@ -363,7 +363,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
return getIntegrityCheck
|
||||
.thenComposeAsync(integrityCheck ->
|
||||
new FileDownloadTask(downloadProvider.injectURLWithCandidates(fileInfo.getDirectDownloadUri()),
|
||||
targetFile, integrityCheck).setName(fileInfo.getFileName()))
|
||||
targetFile.toPath(), integrityCheck).setName(fileInfo.getFileName()))
|
||||
.thenSupplyAsync(targetFile::toPath);
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
|
||||
@@ -29,7 +29,6 @@ import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.ImageView;
|
||||
@@ -125,18 +124,16 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
titleBar.getStyleClass().add("title");
|
||||
titleBar.setLeft(new Label(title));
|
||||
|
||||
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20);
|
||||
hideNode.setCursor(Cursor.HAND);
|
||||
titleBar.setRight(hideNode);
|
||||
FXUtils.onClicked(hideNode, () -> {
|
||||
if (announcementPane != null) {
|
||||
if (Metadata.isDev()) {
|
||||
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
|
||||
}
|
||||
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
|
||||
JFXButton btnHide = new JFXButton();
|
||||
btnHide.setOnAction(e -> {
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
|
||||
if (Metadata.isDev()) {
|
||||
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
|
||||
}
|
||||
});
|
||||
btnHide.getStyleClass().add("announcement-close-button");
|
||||
btnHide.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
|
||||
titleBar.setRight(btnHide);
|
||||
|
||||
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
|
||||
body.setLineSpacing(4);
|
||||
@@ -279,7 +276,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.onClicked(menu, popup::hide);
|
||||
versionNodes = MappedObservableList.create(versions, version -> {
|
||||
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
|
||||
FXUtils.onClicked(node, () -> profile.setSelectedVersion(version.getId()));
|
||||
FXUtils.onClicked(node, () -> {
|
||||
profile.setSelectedVersion(version.getId());
|
||||
popup.hide();
|
||||
});
|
||||
return node;
|
||||
});
|
||||
Bindings.bindContent(menu.getContent(), versionNodes);
|
||||
|
||||
@@ -68,6 +68,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
||||
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
@@ -229,6 +230,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
private static class ModDownloadListPageSkin extends SkinBase<DownloadListPage> {
|
||||
private final JFXListView<RemoteMod> listView = new JFXListView<>();
|
||||
protected ModDownloadListPageSkin(DownloadListPage control) {
|
||||
super(control);
|
||||
|
||||
@@ -449,6 +451,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
boolean disableNext = disableAll || pageOffset == pageCount - 1;
|
||||
nextPageButton.setDisable(disableNext);
|
||||
lastPageButton.setDisable(disableNext);
|
||||
|
||||
listView.scrollTo(0);
|
||||
};
|
||||
|
||||
FXUtils.onChange(control.pageCount, pageCountN -> {
|
||||
@@ -504,7 +508,6 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
});
|
||||
|
||||
JFXListView<RemoteMod> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
||||
FXUtils.onClicked(listView, () -> {
|
||||
@@ -540,7 +543,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
if (StringUtils.isNotBlank(dataItem.getIconUrl())) {
|
||||
imageView.setImage(FXUtils.newRemoteImage(dataItem.getIconUrl(), 40, 40, true, true, true));
|
||||
LOG.debug("Icon: " + dataItem.getIconUrl());
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(dataItem.getIconUrl(), 40, 40, true, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,12 +49,12 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -180,7 +180,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
Controllers.taskDialog(
|
||||
Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest, file.getFile().getIntegrityCheck());
|
||||
var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest.toPath(), file.getFile().getIntegrityCheck());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}),
|
||||
@@ -220,7 +220,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
{
|
||||
ImageView imageView = new ImageView();
|
||||
if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {
|
||||
imageView.setImage(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true));
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 40, 40, true, true));
|
||||
}
|
||||
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
||||
|
||||
@@ -359,7 +359,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
if (StringUtils.isNotBlank(addon.getIconUrl())) {
|
||||
imageView.setImage(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true, true));
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true));
|
||||
}
|
||||
} else {
|
||||
content.setTitle(i18n("mods.broken_dependency.title"));
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CSVTable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -293,9 +293,9 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
if (isDisabled)
|
||||
fileName += ModManager.DISABLED_EXTENSION;
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
new URL(remote.getFile().getUrl()),
|
||||
modManager.getModsDirectory().resolve(fileName).toFile());
|
||||
var task = new FileDownloadTask(
|
||||
URI.create(remote.getFile().getUrl()),
|
||||
modManager.getModsDirectory().resolve(fileName));
|
||||
|
||||
task.setName(remote.getName());
|
||||
return task;
|
||||
|
||||
@@ -28,10 +28,7 @@ import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.CreateAccountPane;
|
||||
@@ -47,7 +44,8 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CancellationException;
|
||||
@@ -75,18 +73,18 @@ public final class Versions {
|
||||
|
||||
public static void downloadModpackImpl(Profile profile, String version, RemoteMod.Version file) {
|
||||
Path modpack;
|
||||
URL downloadURL;
|
||||
URI downloadURL;
|
||||
try {
|
||||
modpack = Files.createTempFile("modpack", ".zip");
|
||||
downloadURL = new URL(file.getFile().getUrl());
|
||||
} catch (IOException e) {
|
||||
downloadURL = new URI(file.getFile().getUrl());
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(downloadURL, modpack.toFile())
|
||||
new FileDownloadTask(downloadURL, modpack)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
if (version != null) {
|
||||
|
||||
@@ -19,21 +19,21 @@ package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
class HMCLDownloadTask extends FileDownloadTask {
|
||||
final class HMCLDownloadTask extends FileDownloadTask {
|
||||
|
||||
private RemoteVersion.Type archiveFormat;
|
||||
private final RemoteVersion.Type archiveFormat;
|
||||
|
||||
public HMCLDownloadTask(RemoteVersion version, Path target) {
|
||||
super(NetworkUtils.toURL(version.getUrl()), target.toFile(), version.getIntegrityCheck());
|
||||
super(URI.create(version.getUrl()), target, version.getIntegrityCheck());
|
||||
archiveFormat = version.getType();
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
super.execute();
|
||||
|
||||
try {
|
||||
Path target = getFile().toPath();
|
||||
|
||||
Path target = getPath();
|
||||
switch (archiveFormat) {
|
||||
case JAR:
|
||||
break;
|
||||
@@ -51,7 +50,7 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
case PACK_XZ:
|
||||
byte[] raw = Files.readAllBytes(target);
|
||||
try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw));
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
|
||||
Pack200Utils.unpack(in, out);
|
||||
}
|
||||
break;
|
||||
@@ -60,7 +59,11 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
throw new IllegalArgumentException("Unknown format: " + archiveFormat);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
getFile().delete();
|
||||
try {
|
||||
Files.deleteIfExists(getPath());
|
||||
} catch (Throwable e2) {
|
||||
e.addSuppressed(e2);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,14 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
|
||||
public class RemoteVersion {
|
||||
|
||||
public static RemoteVersion fetch(UpdateChannel channel, String url) throws IOException {
|
||||
try {
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(URI.create(url)), JsonObject.class);
|
||||
String version = Optional.ofNullable(response.get("version")).map(JsonElement::getAsString).orElseThrow(() -> new IOException("version is missing"));
|
||||
String jarUrl = Optional.ofNullable(response.get("jar")).map(JsonElement::getAsString).orElse(null);
|
||||
String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -122,7 +123,7 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
map.put("version", Metadata.VERSION);
|
||||
map.put("log", LOG.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
String response = NetworkUtils.doPost(URI.create(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
LOG.error("Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
|
||||
@@ -120,5 +120,43 @@ public final class SwingFXUtils {
|
||||
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
|
||||
return wimg;
|
||||
}
|
||||
|
||||
public static WritableImage toFXImage(BufferedImage bimg, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
if (requestedWidth <= 0. || requestedHeight <= 0.) {
|
||||
return toFXImage(bimg, null);
|
||||
}
|
||||
|
||||
int width = (int) requestedWidth;
|
||||
int height = (int) requestedHeight;
|
||||
|
||||
// Calculate actual dimensions if preserveRatio is true
|
||||
if (preserveRatio) {
|
||||
double originalWidth = bimg.getWidth();
|
||||
double originalHeight = bimg.getHeight();
|
||||
double scaleX = requestedWidth / originalWidth;
|
||||
double scaleY = requestedHeight / originalHeight;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
width = (int) (originalWidth * scale);
|
||||
height = (int) (originalHeight * scale);
|
||||
}
|
||||
|
||||
// Create scaled BufferedImage
|
||||
BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
Graphics2D g2d = scaledImage.createGraphics();
|
||||
try {
|
||||
if (smooth) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
}
|
||||
|
||||
g2d.drawImage(bimg, 0, 0, width, height, null);
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
return toFXImage(scaledImage, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1080,6 +1080,23 @@
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
.announcement-close-button {
|
||||
-fx-announcement-close-button-size: 20px;
|
||||
-fx-pref-width: -fx-announcement-close-button-size;
|
||||
-fx-max-width: -fx-announcement-close-button-size;
|
||||
-fx-min-width: -fx-announcement-close-button-size;
|
||||
-fx-pref-height: -fx-announcement-close-button-size;
|
||||
-fx-max-height: -fx-announcement-close-button-size;
|
||||
-fx-min-height: -fx-announcement-close-button-size;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.announcement-close-button .jfx-rippler {
|
||||
-jfx-rippler-fill: -fx-base-check-color;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ account.methods.microsoft.purchase=Buy Minecraft
|
||||
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in.
|
||||
account.methods.microsoft.snapshot.website=Official Website
|
||||
account.methods.offline=Offline
|
||||
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
|
||||
account.methods.offline.name.special_characters=Use only letters, numbers, and underscores (max 16 chars)
|
||||
account.methods.offline.name.invalid=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.\n\
|
||||
\n\
|
||||
\ · Legitimate: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
@@ -930,17 +930,20 @@ modpack.wizard.step.initialization.server=Click here for more information on how
|
||||
modrinth.category.adventure=Adventure
|
||||
modrinth.category.atmosphere=Atmosphere
|
||||
modrinth.category.audio=Audio
|
||||
modrinth.category.babric=Babric
|
||||
modrinth.category.blocks=Blocks
|
||||
modrinth.category.bloom=Bloom
|
||||
modrinth.category.bta-babric=BTA (Babric)
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=Cartoon
|
||||
modrinth.category.challenging=Challenging
|
||||
modrinth.category.colored-lighting=Colored Lighting
|
||||
modrinth.category.core-shaders=Core Shaders
|
||||
modrinth.category.combat=Combat
|
||||
modrinth.category.core-shaders=Core Shaders
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.datapack=Datapack
|
||||
modrinth.category.decoration=Decoration
|
||||
modrinth.category.economy=Economy
|
||||
modrinth.category.entities=Entities
|
||||
@@ -948,6 +951,7 @@ modrinth.category.environment=Environment
|
||||
modrinth.category.equipment=Equipment
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=Fantasy
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.foliage=Foliage
|
||||
modrinth.category.fonts=Fonts
|
||||
modrinth.category.food=Food
|
||||
@@ -957,7 +961,9 @@ modrinth.category.gui=GUI
|
||||
modrinth.category.high=High
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=Items
|
||||
modrinth.category.java-agent=Java Agent
|
||||
modrinth.category.kitchen-sink=Kitchen-Sink
|
||||
modrinth.category.legacy-fabric=Legacy Fabric
|
||||
modrinth.category.library=Library
|
||||
modrinth.category.lightweight=Lightweight
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
@@ -975,8 +981,10 @@ modrinth.category.models=Models
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.multiplayer=Multiplayer
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.nilloader=NilLoader
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=Optimization
|
||||
modrinth.category.ornithe=Ornithe
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=Path Tracing
|
||||
modrinth.category.pbr=PBR
|
||||
@@ -1005,8 +1013,6 @@ modrinth.category.vanilla-like=Vanilla-like
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
modrinth.category.worldgen=Worldgen
|
||||
modrinth.category.datapack=Datapack
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.8x-=8x-
|
||||
modrinth.category.16x=16x
|
||||
modrinth.category.32x=32x
|
||||
|
||||
@@ -435,6 +435,7 @@ folder.shaderpacks=Paquetes de sombreados
|
||||
folder.saves=Mundos
|
||||
folder.schematics=Esquemas
|
||||
folder.screenshots=Capturas de pantalla
|
||||
folder.world=Directorio del mundo
|
||||
|
||||
game=Juegos
|
||||
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, haga clic en <b>Exportar registros de colgado</b> y envíe el archivo exportado a otras personas para que lo analicen.
|
||||
@@ -932,33 +933,50 @@ modpack.wizard.step.initialization.warning=Antes de hacer un modpack, por favor
|
||||
modpack.wizard.step.initialization.server=Haga clic aquí para ver más tutoriales para hacer un modpack de servidor que se pueda actualizar automáticamente.
|
||||
|
||||
modrinth.category.adventure=Aventura
|
||||
modrinth.category.atmosphere=Atmósfera
|
||||
modrinth.category.audio=Audio
|
||||
modrinth.category.babric=Babric
|
||||
modrinth.category.blocks=Bloqueos
|
||||
modrinth.category.bloom=Resplandor
|
||||
modrinth.category.bta-babric=BTA (Babric)
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=Caricatura
|
||||
modrinth.category.challenging=Desafío
|
||||
modrinth.category.core-shaders=Sombreadores de núcleo
|
||||
modrinth.category.colored-lighting=Iluminación de color
|
||||
modrinth.category.combat=Combate
|
||||
modrinth.category.core-shaders=Sombreadores de núcleo
|
||||
modrinth.category.cursed=Maldición
|
||||
modrinth.category.datapack=Paquete de datos
|
||||
modrinth.category.decoration=Decoración
|
||||
modrinth.category.economy=Economía
|
||||
modrinth.category.entities=Entidades
|
||||
modrinth.category.environment=Medio ambiente
|
||||
modrinth.category.equipment=Equipo
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=Fantasía
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.foliage=Vegetación
|
||||
modrinth.category.fonts=Fonts
|
||||
modrinth.category.food=Alimentos
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=Mecánica de juego
|
||||
modrinth.category.gui=GUI
|
||||
modrinth.category.high=Alto
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=Objetos
|
||||
modrinth.category.java-agent=Java Agent
|
||||
modrinth.category.kitchen-sink=Fregadero de cocina
|
||||
modrinth.category.legacy-fabric=Legacy Fabric
|
||||
modrinth.category.library=Biblioteca
|
||||
modrinth.category.lightweight=Peso ligero
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=Locale
|
||||
modrinth.category.low=Bajo
|
||||
modrinth.category.magic=Magia
|
||||
modrinth.category.management=Gestión
|
||||
modrinth.category.medium=Medio
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=Minijuego
|
||||
modrinth.category.misc=Misceláneo
|
||||
@@ -968,13 +986,23 @@ modrinth.category.models=Modelos
|
||||
modrinth.category.modloader=Cargador de mods
|
||||
modrinth.category.multiplayer=Multijugador
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.nilloader=NilLoader
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=Optimización
|
||||
modrinth.category.ornithe=Ornithe
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=Trazado de rutas
|
||||
modrinth.category.pbr=PBR
|
||||
modrinth.category.potato=Mínimo
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=Misiones
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=Realista
|
||||
modrinth.category.reflections=Reflexiones
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.screenshot=Extremo
|
||||
modrinth.category.semi-realistic=Semirrealista
|
||||
modrinth.category.shadows=Sombras
|
||||
modrinth.category.simplistic=Simplista
|
||||
modrinth.category.social=Social
|
||||
modrinth.category.spigot=Spigot
|
||||
@@ -985,12 +1013,11 @@ modrinth.category.themed=Temática
|
||||
modrinth.category.transportation=Transporte
|
||||
modrinth.category.tweaks=Ajustes
|
||||
modrinth.category.utility=Utilidad
|
||||
modrinth.category.vanilla=Vainilla
|
||||
modrinth.category.vanilla-like=Similar a la vainilla
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
modrinth.category.worldgen=Generación de mundos
|
||||
modrinth.category.datapack=Paquete de datos
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.8x-=8x-
|
||||
modrinth.category.16x=16x
|
||||
modrinth.category.32x=32x
|
||||
@@ -1072,7 +1099,12 @@ world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra
|
||||
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
|
||||
world.backup.delete=Eliminar esta copia de seguridad
|
||||
world.backup.processing=Creando nueva copia de seguridad ...
|
||||
world.datapack=Gestionar paquetes de datos
|
||||
world.chunkbase=Chunk Base
|
||||
world.chunkbase.end_city=Ciudad del End
|
||||
world.chunkbase.seed_map=Vista previa de la generación mundial
|
||||
world.chunkbase.stronghold=Fortaleza
|
||||
world.chunkbase.nether_fortress=Fortaleza del Nether
|
||||
world.datapack=Paquetes de datos
|
||||
world.datetime=Jugado por última vez en %s
|
||||
world.download=Descargar Mundo
|
||||
world.download.title=Descargar mundo - %1s
|
||||
@@ -1117,7 +1149,8 @@ world.info.time=Tiempo de juego
|
||||
world.info.time.format=%s días
|
||||
world.locked=En uso
|
||||
world.manage=Mundos
|
||||
world.manage.title=Mundos - %s
|
||||
world.manage.button=Administrar
|
||||
world.manage.title=Mundo - %s
|
||||
world.name=Nombre del mundo
|
||||
world.name.enter=Introducir el nombre del mundo
|
||||
world.show_all=Mostrar todo
|
||||
|
||||
@@ -433,6 +433,7 @@ folder.shaderpacks=Пакеты шейдеров
|
||||
folder.saves=Миры
|
||||
folder.schematics=Схемы
|
||||
folder.screenshots=Снимки экрана
|
||||
folder.world=Папка мира
|
||||
|
||||
game=Игры
|
||||
game.crash.feedback=<b>Пожалуйста, не делитесь скриншотами этого интерфейса с другими!</b> Если вы просите помощи у других, нажмите <b>«Экспорт журналов с ошибками»</b> и отправьте экспортированный файл другим для анализа.
|
||||
@@ -931,33 +932,50 @@ modpack.wizard.step.initialization.warning=Перед созданием мод
|
||||
modpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться.
|
||||
|
||||
modrinth.category.adventure=Приключение
|
||||
modrinth.category.atmosphere=Атмосфера
|
||||
modrinth.category.audio=Аудио
|
||||
modrinth.category.babric=Babric
|
||||
modrinth.category.blocks=Блоки
|
||||
modrinth.category.bloom=Свечение
|
||||
modrinth.category.bta-babric=BTA (Babric)
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=Мультяшный
|
||||
modrinth.category.challenging=Вызов
|
||||
modrinth.category.core-shaders=Ядро шейдеров
|
||||
modrinth.category.colored-lighting=Цветное освещение
|
||||
modrinth.category.combat=Бой
|
||||
modrinth.category.core-shaders=Ядро шейдеров
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.datapack=Набор данных
|
||||
modrinth.category.decoration=Украшения
|
||||
modrinth.category.economy=Экономика
|
||||
modrinth.category.entities=Сущности
|
||||
modrinth.category.environment=Окружающая среда
|
||||
modrinth.category.equipment=Оборудование
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=Фэнтези
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.foliage=Растительность
|
||||
modrinth.category.fonts=Шрифты
|
||||
modrinth.category.food=Еда
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=Игровая механика
|
||||
modrinth.category.gui=ГИП
|
||||
modrinth.category.high=Высокий
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=Предметы
|
||||
modrinth.category.java-agent=Java Agent
|
||||
modrinth.category.kitchen-sink=Kitchen-Sink
|
||||
modrinth.category.legacy-fabric=Legacy Fabric
|
||||
modrinth.category.library=Библиотека
|
||||
modrinth.category.lightweight=Легкий
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=Язык
|
||||
modrinth.category.low=Низкий
|
||||
modrinth.category.magic=Магия
|
||||
modrinth.category.management=Управление
|
||||
modrinth.category.medium=Средний
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=Мини-игра
|
||||
modrinth.category.misc=Разное
|
||||
@@ -967,13 +985,23 @@ modrinth.category.models=Модели
|
||||
modrinth.category.modloader=Мод-загрузчик
|
||||
modrinth.category.multiplayer=Сетевая игра
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.nilloader=NilLoader
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=Оптимизация
|
||||
modrinth.category.ornithe=Ornithe
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=Трассировка пути
|
||||
modrinth.category.pbr=PBR
|
||||
modrinth.category.potato=Минимум
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=Квесты
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=Реалистичный
|
||||
modrinth.category.reflections=Отражения
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.screenshot=Экстрим
|
||||
modrinth.category.semi-realistic=Полуреалистичный
|
||||
modrinth.category.shadows=Тени
|
||||
modrinth.category.simplistic=Упрощенный
|
||||
modrinth.category.social=Социальная
|
||||
modrinth.category.spigot=Spigot
|
||||
@@ -984,12 +1012,11 @@ modrinth.category.themed=Тематический
|
||||
modrinth.category.transportation=Транспорт
|
||||
modrinth.category.tweaks=Усовершенствование
|
||||
modrinth.category.utility=Утилиты
|
||||
modrinth.category.vanilla=Ванильное
|
||||
modrinth.category.vanilla-like=Ванильное
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
modrinth.category.worldgen=Генерация мира
|
||||
modrinth.category.datapack=Набор данных
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.8x-=8x-
|
||||
modrinth.category.16x=16x
|
||||
modrinth.category.32x=32x
|
||||
@@ -1071,7 +1098,12 @@ world.backup.create.locked=В настоящее время мир находи
|
||||
world.backup.create.success=Успешно создано новое резервное копирование: %s
|
||||
world.backup.delete=Удалить эту резервную копию
|
||||
world.backup.processing=Создание новой резервной копии ...
|
||||
world.datapack=Управлять наборами данных
|
||||
world.chunkbase=Chunk Base
|
||||
world.chunkbase.end_city=Город Края
|
||||
world.chunkbase.seed_map=Предпросмотр генерации мира
|
||||
world.chunkbase.stronghold=Крепость
|
||||
world.chunkbase.nether_fortress=Крепость Нижнего мира
|
||||
world.datapack=Наборы данных
|
||||
world.datetime=Последний запуск игры %s
|
||||
world.download=Скачать мир
|
||||
world.download.title=Скачать мир - %1s
|
||||
@@ -1116,7 +1148,8 @@ world.info.time=Время игры
|
||||
world.info.time.format=%s дн.
|
||||
world.locked=В эксплуатации
|
||||
world.manage=Миры
|
||||
world.manage.title=Миры - %s
|
||||
world.manage.button=Управлять
|
||||
world.manage.title=Мир - %s
|
||||
world.name=Название мира
|
||||
world.name.enter=Введите название мира
|
||||
world.show_all=Показать все
|
||||
|
||||
@@ -131,7 +131,7 @@ account.methods.ban_query=查詢帳戶是否被封禁
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。請下載官方版本進行登入。
|
||||
account.methods.microsoft.snapshot.website=官方網站
|
||||
account.methods.offline=離線模式
|
||||
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名
|
||||
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名,且長度不超過 16 個字元
|
||||
account.methods.offline.name.invalid=遊戲使用者名稱建議僅使用英文字母、數字及底線,且長度不超過 16 個字元。\n\
|
||||
\n\
|
||||
\ · 一些有效的使用者名稱:HuangYu、huang_Yu、Huang_Yu_123;\n\
|
||||
@@ -744,17 +744,20 @@ modpack.wizard.step.initialization.server=點選此處查看有關伺服器自
|
||||
modrinth.category.adventure=冒險
|
||||
modrinth.category.atmosphere=氛圍
|
||||
modrinth.category.audio=聲音
|
||||
modrinth.category.babric=Babric
|
||||
modrinth.category.blocks=方塊
|
||||
modrinth.category.bloom=泛光
|
||||
modrinth.category.bta-babric=BTA (Babric)
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=卡通
|
||||
modrinth.category.challenging=高難度
|
||||
modrinth.category.colored-lighting=彩色光照
|
||||
modrinth.category.core-shaders=核心著色器
|
||||
modrinth.category.combat=戰鬥
|
||||
modrinth.category.core-shaders=核心著色器
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.datapack=資料包
|
||||
modrinth.category.decoration=裝飾
|
||||
modrinth.category.economy=經濟
|
||||
modrinth.category.entities=實體
|
||||
@@ -762,6 +765,7 @@ modrinth.category.environment=環境
|
||||
modrinth.category.equipment=裝備
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=幻想
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.foliage=植被
|
||||
modrinth.category.fonts=字體
|
||||
modrinth.category.food=食物
|
||||
@@ -771,7 +775,9 @@ modrinth.category.gui=GUI
|
||||
modrinth.category.high=高
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=物品
|
||||
modrinth.category.java-agent=Java Agent
|
||||
modrinth.category.kitchen-sink=大雜燴
|
||||
modrinth.category.legacy-fabric=Legacy Fabric
|
||||
modrinth.category.library=支援庫
|
||||
modrinth.category.lightweight=輕量
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
@@ -789,8 +795,10 @@ modrinth.category.models=模型
|
||||
modrinth.category.modloader=ModLoader
|
||||
modrinth.category.multiplayer=多人
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.nilloader=NilLoader
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=最佳化
|
||||
modrinth.category.ornithe=Ornithe
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=路径追踪
|
||||
modrinth.category.pbr=PBR
|
||||
@@ -819,8 +827,6 @@ modrinth.category.vanilla-like=類原生
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
modrinth.category.worldgen=世界生成
|
||||
modrinth.category.datapack=資料包
|
||||
modrinth.category.folia=Folia
|
||||
|
||||
mods=模組
|
||||
mods.add=新增模組
|
||||
|
||||
@@ -139,7 +139,7 @@ account.methods.ban_query=检测账户是否被封禁
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。请下载官方版本来登录微软账户。
|
||||
account.methods.microsoft.snapshot.website=官方网站
|
||||
account.methods.offline=离线模式
|
||||
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名
|
||||
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名,且长度不超过 16 个字符
|
||||
account.methods.offline.name.invalid=游戏用户名建议仅使用英文字母、数字及下划线,且长度不超过 16 个字符。\n\
|
||||
\n\
|
||||
\ · 一些合法用户名:HuangYu、huang_Yu、Huang_Yu_123;\n\
|
||||
@@ -754,17 +754,20 @@ modpack.wizard.step.initialization.server=点击此处查看有关服务器自
|
||||
modrinth.category.adventure=冒险
|
||||
modrinth.category.atmosphere=氛围
|
||||
modrinth.category.audio=声音
|
||||
modrinth.category.babric=Babric
|
||||
modrinth.category.blocks=方块
|
||||
modrinth.category.bloom=泛光
|
||||
modrinth.category.bta-babric=BTA (Babric)
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=卡通
|
||||
modrinth.category.challenging=高难度
|
||||
modrinth.category.colored-lighting=彩色光照
|
||||
modrinth.category.core-shaders=核心着色器
|
||||
modrinth.category.combat=战斗
|
||||
modrinth.category.core-shaders=核心着色器
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.datapack=数据包
|
||||
modrinth.category.decoration=装饰
|
||||
modrinth.category.economy=经济
|
||||
modrinth.category.entities=实体
|
||||
@@ -772,6 +775,7 @@ modrinth.category.environment=环境
|
||||
modrinth.category.equipment=装备
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=幻想
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.foliage=植被
|
||||
modrinth.category.fonts=字体
|
||||
modrinth.category.food=食物
|
||||
@@ -781,7 +785,9 @@ modrinth.category.gui=GUI
|
||||
modrinth.category.high=高
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=物品
|
||||
modrinth.category.java-agent=Java Agent
|
||||
modrinth.category.kitchen-sink=大杂烩
|
||||
modrinth.category.legacy-fabric=Legacy Fabric
|
||||
modrinth.category.library=支持库
|
||||
modrinth.category.lightweight=轻量
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
@@ -799,8 +805,10 @@ modrinth.category.models=模型
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.multiplayer=多人
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.nilloader=NilLoader
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=优化
|
||||
modrinth.category.ornithe=Ornithe
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=路径追踪
|
||||
modrinth.category.pbr=PBR
|
||||
@@ -829,8 +837,6 @@ modrinth.category.vanilla-like=类原生
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
modrinth.category.worldgen=世界生成
|
||||
modrinth.category.datapack=数据包
|
||||
modrinth.category.folia=Folia
|
||||
|
||||
mods=模组
|
||||
mods.add=添加模组
|
||||
|
||||
@@ -28,40 +28,53 @@ public final class Main {
|
||||
private Main() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current Java version is compatible with HMCL.
|
||||
*/
|
||||
static boolean checkJavaVersion(String javaVersion) {
|
||||
if (javaVersion == null) {
|
||||
return false;
|
||||
static int findFirstNotNumber(String str, int start) {
|
||||
if (start >= str.length())
|
||||
return -1;
|
||||
|
||||
char ch = str.charAt(start);
|
||||
if (ch < '0' || ch > '9')
|
||||
return -1;
|
||||
|
||||
for (int i = start + 1; i < str.length(); i++) {
|
||||
ch = str.charAt(i);
|
||||
if (ch < '0' || ch > '9')
|
||||
return i;
|
||||
}
|
||||
return str.length();
|
||||
}
|
||||
|
||||
static int getJavaFeatureVersion(String javaVersion) {
|
||||
if (javaVersion == null)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
int major;
|
||||
int dot = javaVersion.indexOf('.');
|
||||
int end = findFirstNotNumber(javaVersion, 0);
|
||||
if (end < 0)
|
||||
return -1; // No valid version number found
|
||||
|
||||
if (dot >= 0) {
|
||||
major = Integer.parseInt(javaVersion.substring(0, dot));
|
||||
if (major == 1 && dot < javaVersion.length() - 1) {
|
||||
int begin = dot + 1;
|
||||
dot = javaVersion.indexOf('.', begin);
|
||||
int major = Integer.parseInt(javaVersion.substring(0, end));
|
||||
if (major > 1)
|
||||
return major;
|
||||
|
||||
major = dot > begin
|
||||
? Integer.parseInt(javaVersion.substring(begin, dot))
|
||||
: Integer.parseInt(javaVersion.substring(begin));
|
||||
}
|
||||
} else {
|
||||
major = Integer.parseInt(javaVersion);
|
||||
}
|
||||
if (major < 1)
|
||||
return -1; // Invalid major version
|
||||
|
||||
return major >= MINIMUM_JAVA_VERSION;
|
||||
// Java 1.x versions
|
||||
int start = end + 1;
|
||||
end = findFirstNotNumber(javaVersion, start);
|
||||
|
||||
if (end < 0)
|
||||
return -1; // No valid minor version found
|
||||
|
||||
return Integer.parseInt(javaVersion.substring(start, end));
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
return -1; // version number is too long
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
if (checkJavaVersion(System.getProperty("java.version"))) {
|
||||
if (getJavaFeatureVersion(System.getProperty("java.version")) >= MINIMUM_JAVA_VERSION) {
|
||||
EntryPoint.main(args);
|
||||
} else {
|
||||
String errorMessage = BootProperties.getResourceBundle().getString("boot.unsupported_java_version");
|
||||
|
||||
@@ -19,21 +19,30 @@ package org.jackhuang.hmcl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public final class MainTest {
|
||||
@Test
|
||||
public void testCheckJavaVersion() {
|
||||
assertFalse(Main.checkJavaVersion("1.6.0"));
|
||||
assertFalse(Main.checkJavaVersion("1.6.0_45"));
|
||||
assertFalse(Main.checkJavaVersion("1.7.0"));
|
||||
assertFalse(Main.checkJavaVersion("1.7.0_80"));
|
||||
assertFalse(Main.checkJavaVersion("1.8"));
|
||||
assertFalse(Main.checkJavaVersion("1.8.0_321"));
|
||||
public void testGetJavaFeatureVersion() {
|
||||
assertEquals(6, Main.getJavaFeatureVersion("1.6.0"));
|
||||
assertEquals(6, Main.getJavaFeatureVersion("1.6.0_45"));
|
||||
assertEquals(7, Main.getJavaFeatureVersion("1.7.0"));
|
||||
assertEquals(7, Main.getJavaFeatureVersion("1.7.0_80"));
|
||||
assertEquals(8, Main.getJavaFeatureVersion("1.8"));
|
||||
assertEquals(8, Main.getJavaFeatureVersion("1.8u321"));
|
||||
assertEquals(8, Main.getJavaFeatureVersion("1.8.0_321"));
|
||||
assertEquals(11, Main.getJavaFeatureVersion("11"));
|
||||
assertEquals(11, Main.getJavaFeatureVersion("11.0.26"));
|
||||
assertEquals(21, Main.getJavaFeatureVersion("21"));
|
||||
assertEquals(26, Main.getJavaFeatureVersion("26-ea"));
|
||||
|
||||
assertTrue(Main.checkJavaVersion("11"));
|
||||
assertTrue(Main.checkJavaVersion("11.0.26"));
|
||||
assertTrue(Main.checkJavaVersion("21"));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion(null));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion(""));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion("0"));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion("0.8"));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion("abc"));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion("1.abc"));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion(".1"));
|
||||
assertEquals(-1, Main.getJavaFeatureVersion("1111111111111111111111"));
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
@@ -96,7 +96,7 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation.toFile(),
|
||||
new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation,
|
||||
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||
.orElse(null))
|
||||
@@ -110,9 +110,9 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
|
||||
private AuthlibInjectorVersionInfo getLatestArtifactInfo() throws IOException {
|
||||
IOException exception = null;
|
||||
for (URL url : downloadProvider.get().injectURLWithCandidates(LATEST_BUILD_URL)) {
|
||||
for (URI url : downloadProvider.get().injectURLWithCandidates(LATEST_BUILD_URL)) {
|
||||
try {
|
||||
return HttpRequest.GET(url.toExternalForm()).getJson(AuthlibInjectorVersionInfo.class);
|
||||
return HttpRequest.GET(url.toString()).getJson(AuthlibInjectorVersionInfo.class);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
if (exception == null) {
|
||||
exception = new IOException("Failed to fetch authlib-injector artifact info");
|
||||
|
||||
+13
-15
@@ -17,13 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
|
||||
import static org.jackhuang.hmcl.util.io.NetworkUtils.toURL;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthlibInjectorProvider implements YggdrasilProvider {
|
||||
@@ -35,33 +33,33 @@ public class AuthlibInjectorProvider implements YggdrasilProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getAuthenticationURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/authenticate");
|
||||
public URI getAuthenticationURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/authenticate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getRefreshmentURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/refresh");
|
||||
public URI getRefreshmentURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/refresh");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getValidationURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/validate");
|
||||
public URI getValidationURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/validate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getInvalidationURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/invalidate");
|
||||
public URI getInvalidationURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/invalidate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {
|
||||
return toURL(apiRoot + "api/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin");
|
||||
public URI getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {
|
||||
return URI.create(apiRoot + "api/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException {
|
||||
return toURL(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
|
||||
public URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException {
|
||||
return URI.create(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+18
-17
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -25,14 +24,15 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -58,17 +58,14 @@ public class AuthlibInjectorServer implements Observable {
|
||||
public static AuthlibInjectorServer locateServer(String url) throws IOException {
|
||||
try {
|
||||
url = addHttpsIfMissing(url);
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
|
||||
|
||||
HttpURLConnection conn = NetworkUtils.createHttpConnection(URI.create(url));
|
||||
String ali = conn.getHeaderField("x-authlib-injector-api-location");
|
||||
if (ali != null) {
|
||||
URL absoluteAli = new URL(conn.getURL(), ali);
|
||||
URI absoluteAli = conn.getURL().toURI().resolve(ali);
|
||||
if (!urlEqualsIgnoreSlash(url, absoluteAli.toString())) {
|
||||
conn.disconnect();
|
||||
url = absoluteAli.toString();
|
||||
conn = (HttpURLConnection) absoluteAli.openConnection();
|
||||
conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
|
||||
conn = NetworkUtils.createHttpConnection(absoluteAli);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,22 +74,26 @@ public class AuthlibInjectorServer implements Observable {
|
||||
|
||||
try {
|
||||
AuthlibInjectorServer server = new AuthlibInjectorServer(url);
|
||||
server.refreshMetadata(new String(conn.getInputStream().readAllBytes(), UTF_8));
|
||||
server.refreshMetadata(NetworkUtils.readFullyAsString(conn));
|
||||
return server;
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException | URISyntaxException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String addHttpsIfMissing(String url) {
|
||||
String lowercased = url.toLowerCase(Locale.ROOT);
|
||||
if (!lowercased.startsWith("http://") && !lowercased.startsWith("https://")) {
|
||||
url = "https://" + url;
|
||||
private static String addHttpsIfMissing(String url) throws IOException {
|
||||
URI uri = URI.create(url);
|
||||
|
||||
if (uri.getScheme() == null) {
|
||||
return "https://" + url;
|
||||
} else if (!NetworkUtils.isHttpUri(uri)) {
|
||||
throw new IOException("Yggdrasil server should be an HTTP or HTTPS URI, but got: " + url);
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private static boolean urlEqualsIgnoreSlash(String a, String b) {
|
||||
@@ -103,7 +104,7 @@ public class AuthlibInjectorServer implements Observable {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
private String url;
|
||||
private final String url;
|
||||
@Nullable
|
||||
private String metadataResponse;
|
||||
private long metadataTimestamp;
|
||||
|
||||
@@ -34,7 +34,8 @@ import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -164,7 +165,7 @@ public class MicrosoftService {
|
||||
.accept("application/json").createConnection();
|
||||
|
||||
if (request.getResponseCode() != 200) {
|
||||
throw new ResponseCodeException(new URL("https://api.minecraftservices.com/entitlements/mcstore"), request.getResponseCode());
|
||||
throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/entitlements/mcstore"), request.getResponseCode());
|
||||
}
|
||||
|
||||
// Get Minecraft Account UUID
|
||||
@@ -247,22 +248,22 @@ public class MicrosoftService {
|
||||
if (responseCode == HTTP_NOT_FOUND) {
|
||||
throw new NoMinecraftJavaEditionProfileException();
|
||||
} else if (responseCode != 200) {
|
||||
throw new ResponseCodeException(new URL("https://api.minecraftservices.com/minecraft/profile"), responseCode);
|
||||
throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/minecraft/profile"), responseCode);
|
||||
}
|
||||
|
||||
String result = NetworkUtils.readData(conn);
|
||||
String result = NetworkUtils.readFullyAsString(conn);
|
||||
return JsonUtils.fromNonNullJson(result, MinecraftProfileResponse.class);
|
||||
}
|
||||
|
||||
public Optional<CompleteGameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
|
||||
return Optional.ofNullable(GSON.fromJson(request(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
|
||||
}
|
||||
|
||||
public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
try {
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile/skins"));
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection(URI.create("https://api.minecraftservices.com/minecraft/profile/skins"));
|
||||
con.setRequestMethod("POST");
|
||||
con.setRequestProperty("Authorization", "Bearer " + accessToken);
|
||||
con.setDoOutput(true);
|
||||
@@ -273,21 +274,21 @@ public class MicrosoftService {
|
||||
}
|
||||
}
|
||||
|
||||
String response = NetworkUtils.readData(con);
|
||||
String response = NetworkUtils.readFullyAsString(con);
|
||||
if (StringUtils.isBlank(response)) {
|
||||
if (con.getResponseCode() / 100 != 2)
|
||||
throw new ResponseCodeException(con.getURL(), con.getResponseCode());
|
||||
throw new ResponseCodeException(con.getURL().toURI(), con.getResponseCode());
|
||||
} else {
|
||||
MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class);
|
||||
if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2)
|
||||
throw new AuthenticationException("Failed to upload skin, response code: " + con.getResponseCode() + ", response: " + response);
|
||||
}
|
||||
} catch (IOException | JsonParseException e) {
|
||||
} catch (IOException | JsonParseException | URISyntaxException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String request(URL url, Object payload) throws AuthenticationException {
|
||||
private static String request(URI url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url);
|
||||
|
||||
@@ -33,14 +33,11 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
@@ -169,7 +166,7 @@ public class Skin {
|
||||
String realCslApi = type == Type.LITTLE_SKIN
|
||||
? "https://littleskin.cn/csl"
|
||||
: StringUtils.removeSuffix(Lang.requireNonNullElse(cslApi, ""), "/");
|
||||
return Task.composeAsync(() -> new GetTask(new URL(String.format("%s/%s.json", realCslApi, username))))
|
||||
return Task.composeAsync(() -> new GetTask(URI.create(String.format("%s/%s.json", realCslApi, username))))
|
||||
.thenComposeAsync(json -> {
|
||||
SkinJson result = JsonUtils.GSON.fromJson(json, SkinJson.class);
|
||||
|
||||
@@ -179,8 +176,8 @@ public class Skin {
|
||||
|
||||
return Task.allOf(
|
||||
Task.supplyAsync(result::getModel),
|
||||
result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(new URL(String.format("%s/textures/%s", realCslApi, result.getHash())), 3),
|
||||
result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(new URL(String.format("%s/textures/%s", realCslApi, result.getCapeHash())), 3)
|
||||
result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(URI.create(String.format("%s/textures/%s", realCslApi, result.getHash())), 3),
|
||||
result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(URI.create(String.format("%s/textures/%s", realCslApi, result.getCapeHash())), 3)
|
||||
);
|
||||
}).thenApplyAsync(result -> {
|
||||
if (result == null) {
|
||||
@@ -232,8 +229,8 @@ public class Skin {
|
||||
|
||||
private static class FetchBytesTask extends FetchTask<InputStream> {
|
||||
|
||||
public FetchBytesTask(URL url, int retry) {
|
||||
super(Collections.singletonList(url), retry);
|
||||
public FetchBytesTask(URI uri, int retry) {
|
||||
super(List.of(uri), retry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,7 +244,7 @@ public class Skin {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context getContext(URLConnection conn, boolean checkETag) throws IOException {
|
||||
protected Context getContext(URLConnection connection, boolean checkETag, String bmclapiHash) throws IOException {
|
||||
return new Context() {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@@ -263,7 +260,7 @@ public class Skin {
|
||||
setResult(new ByteArrayInputStream(baos.toByteArray()));
|
||||
|
||||
if (checkETag) {
|
||||
repository.cacheBytes(baos.toByteArray(), conn);
|
||||
repository.cacheBytes(connection, baos.toByteArray());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -27,13 +27,13 @@ import java.util.UUID;
|
||||
*/
|
||||
public interface YggdrasilProvider {
|
||||
|
||||
URL getAuthenticationURL() throws AuthenticationException;
|
||||
URI getAuthenticationURL() throws AuthenticationException;
|
||||
|
||||
URL getRefreshmentURL() throws AuthenticationException;
|
||||
URI getRefreshmentURL() throws AuthenticationException;
|
||||
|
||||
URL getValidationURL() throws AuthenticationException;
|
||||
URI getValidationURL() throws AuthenticationException;
|
||||
|
||||
URL getInvalidationURL() throws AuthenticationException;
|
||||
URI getInvalidationURL() throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* URL to upload skin.
|
||||
@@ -51,8 +51,8 @@ public interface YggdrasilProvider {
|
||||
* @throws AuthenticationException if url cannot be generated. e.g. some parameter or query is malformed.
|
||||
* @throws UnsupportedOperationException if the Yggdrasil provider does not support third-party skin uploading.
|
||||
*/
|
||||
URL getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException;
|
||||
URI getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException;
|
||||
|
||||
URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException;
|
||||
URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException;
|
||||
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -160,7 +160,7 @@ public class YggdrasilService {
|
||||
request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis);
|
||||
}
|
||||
}
|
||||
requireEmpty(NetworkUtils.readData(con));
|
||||
requireEmpty(NetworkUtils.readFullyAsString(con));
|
||||
} catch (IOException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
@@ -227,12 +227,12 @@ public class YggdrasilService {
|
||||
}
|
||||
}
|
||||
|
||||
private static String request(URL url, Object payload) throws AuthenticationException {
|
||||
private static String request(URI uri, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url);
|
||||
return NetworkUtils.doGet(uri);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
return NetworkUtils.doPost(uri, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
throw new ServerDisconnectException(e);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -59,21 +59,21 @@ public class AdaptedDownloadProvider implements DownloadProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
public List<URI> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return downloadProviderCandidates.stream()
|
||||
.flatMap(d -> d.getAssetObjectCandidates(assetObjectLocation).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLWithCandidates(String baseURL) {
|
||||
public List<URI> injectURLWithCandidates(String baseURL) {
|
||||
return downloadProviderCandidates.stream()
|
||||
.flatMap(d -> d.injectURLWithCandidates(baseURL).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLsWithCandidates(List<String> urls) {
|
||||
public List<URI> injectURLsWithCandidates(List<String> urls) {
|
||||
return downloadProviderCandidates.stream()
|
||||
.flatMap(d -> d.injectURLsWithCandidates(urls).stream())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -51,17 +51,17 @@ public class AutoDownloadProvider implements DownloadProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
public List<URI> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return fileProvider.getAssetObjectCandidates(assetObjectLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLWithCandidates(String baseURL) {
|
||||
public List<URI> injectURLWithCandidates(String baseURL) {
|
||||
return fileProvider.injectURLWithCandidates(baseURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLsWithCandidates(List<String> urls) {
|
||||
public List<URI> injectURLsWithCandidates(List<String> urls) {
|
||||
return fileProvider.injectURLsWithCandidates(urls);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,8 +71,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
pair("http://files.minecraftforge.net/maven", apiRoot + "/maven"),
|
||||
pair("https://files.minecraftforge.net/maven", apiRoot + "/maven"),
|
||||
pair("https://maven.minecraftforge.net", apiRoot + "/maven"),
|
||||
pair("https://maven.neoforged.net/releases/net/neoforged/forge", apiRoot + "/maven/net/neoforged/forge"),
|
||||
pair("https://maven.neoforged.net/releases/net/neoforged/neoforge", apiRoot + "/maven/net/neoforged/neoforge"),
|
||||
pair("https://maven.neoforged.net/releases/", apiRoot + "/maven/"),
|
||||
pair("http://dl.liteloader.com/versions/versions.json", apiRoot + "/maven/com/mumfrey/liteloader/versions.json"),
|
||||
pair("http://dl.liteloader.com/versions", apiRoot + "/maven"),
|
||||
pair("https://meta.fabricmc.net", apiRoot + "/fabric-meta"),
|
||||
|
||||
@@ -29,13 +29,14 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -43,7 +44,6 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
public class DefaultCacheRepository extends CacheRepository {
|
||||
private Path librariesDir;
|
||||
private Path indexFile;
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private Index index = null;
|
||||
|
||||
public DefaultCacheRepository() {
|
||||
@@ -64,10 +64,13 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
if (Files.isRegularFile(indexFile)) {
|
||||
index = JsonUtils.fromNonNullJson(Files.readString(indexFile), Index.class);
|
||||
}
|
||||
else
|
||||
index = JsonUtils.fromJsonFile(indexFile, Index.class);
|
||||
if (index == null) {
|
||||
throw new JsonParseException("Index file is empty or invalid");
|
||||
}
|
||||
} else {
|
||||
index = new Index();
|
||||
}
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.warning("Unable to read index file", e);
|
||||
index = new Index();
|
||||
@@ -82,7 +85,7 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
* If cannot be verified, the library will not be cached.
|
||||
*
|
||||
* @param library the library being cached
|
||||
* @param jar the file of library
|
||||
* @param jar the file of library
|
||||
*/
|
||||
public void tryCacheLibrary(Library library, Path jar) {
|
||||
lock.readLock().lock();
|
||||
@@ -171,8 +174,8 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
* Caches the library file to repository.
|
||||
*
|
||||
* @param library the library to cache
|
||||
* @param path the file being cached, must be verified
|
||||
* @param forge true if this library is provided by Forge
|
||||
* @param path the file being cached, must be verified
|
||||
* @param forge true if this library is provided by Forge
|
||||
* @return cached file location
|
||||
* @throws IOException if failed to calculate hash code of {@code path} or copy the file to cache
|
||||
*/
|
||||
@@ -200,25 +203,30 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
private void saveIndex() {
|
||||
if (indexFile == null || index == null) return;
|
||||
try {
|
||||
FileUtils.writeText(indexFile, JsonUtils.GSON.toJson(index));
|
||||
Files.createDirectories(indexFile.getParent());
|
||||
FileUtils.saveSafely(indexFile, outputStream -> {
|
||||
try (var writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) {
|
||||
JsonUtils.GSON.toJson(index, writer);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unable to save index.json", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {
|
||||
* "libraries": {
|
||||
* // allow a library has multiple hash code.
|
||||
* [
|
||||
* "name": "net.minecraftforge:forge:1.11.2-13.20.0.2345",
|
||||
* "hash": "blablabla",
|
||||
* "type": "forge"
|
||||
* ]
|
||||
* }
|
||||
* // assets and versions will not be included in index.
|
||||
* }
|
||||
*/
|
||||
/// ```json
|
||||
/// {
|
||||
/// "libraries": {
|
||||
/// // allow a library has multiple hash code.
|
||||
/// [
|
||||
/// "name": "net.minecraftforge:forge:1.11.2-13.20.0.2345",
|
||||
/// "hash": "blablabla",
|
||||
/// "type": "forge"
|
||||
/// ]
|
||||
/// }
|
||||
/// }
|
||||
///```
|
||||
/// assets and versions will not be included in index.
|
||||
private static final class Index implements Validation {
|
||||
private final Set<LibraryIndex> libraries;
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
|
||||
|
||||
VersionList<?> versionList = getVersionList(libraryId);
|
||||
return Task.fromCompletableFuture(versionList.loadAsync(gameVersion))
|
||||
return versionList.loadAsync(gameVersion)
|
||||
.thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)
|
||||
.orElseThrow(() -> new IOException("Remote library " + libraryId + " has no version " + libraryVersion))))
|
||||
.withStage(String.format("hmcl.install.%s:%s", libraryId, libraryVersion));
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -35,8 +32,8 @@ public interface DownloadProvider {
|
||||
|
||||
String getAssetBaseURL();
|
||||
|
||||
default List<URL> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return Collections.singletonList(NetworkUtils.toURL(getAssetBaseURL() + assetObjectLocation));
|
||||
default List<URI> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return List.of(URI.create(getAssetBaseURL() + assetObjectLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,11 +56,11 @@ public interface DownloadProvider {
|
||||
* @param baseURL original URL provided by Mojang and Forge.
|
||||
* @return the URL that is equivalent to [baseURL], but belongs to your own service provider.
|
||||
*/
|
||||
default List<URL> injectURLWithCandidates(String baseURL) {
|
||||
return Collections.singletonList(NetworkUtils.toURL(injectURL(baseURL)));
|
||||
default List<URI> injectURLWithCandidates(String baseURL) {
|
||||
return List.of(URI.create(injectURL(baseURL)));
|
||||
}
|
||||
|
||||
default List<URL> injectURLsWithCandidates(List<String> urls) {
|
||||
default List<URI> injectURLsWithCandidates(List<String> urls) {
|
||||
return urls.stream().flatMap(url -> injectURLWithCandidates(url).stream()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -40,44 +41,40 @@ public class MultipleSourceVersionList extends VersionList<RemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> loadAsync() {
|
||||
public Task<?> loadAsync() {
|
||||
throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
public Task<?> refreshAsync() {
|
||||
throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list.");
|
||||
}
|
||||
|
||||
private CompletableFuture<?> refreshAsync(String gameVersion, int sourceIndex) {
|
||||
private Task<?> refreshAsync(String gameVersion, int sourceIndex) {
|
||||
VersionList<?> versionList = backends[sourceIndex];
|
||||
CompletableFuture<Void> future = versionList.refreshAsync(gameVersion)
|
||||
.thenRunAsync(() -> {
|
||||
return versionList.refreshAsync(gameVersion)
|
||||
.thenComposeAsync(() -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
versions.putAll(gameVersion, versionList.getVersions(gameVersion));
|
||||
} catch (Exception e) {
|
||||
if (sourceIndex == backends.length - 1) {
|
||||
LOG.warning("Failed to fetch versions list from all sources", e);
|
||||
throw e;
|
||||
} else {
|
||||
LOG.warning("Failed to fetch versions list and try to fetch from other source", e);
|
||||
return refreshAsync(gameVersion, sourceIndex + 1);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (sourceIndex == backends.length - 1) {
|
||||
return future;
|
||||
} else {
|
||||
return future.<CompletableFuture<?>>handle((ignore, e) -> {
|
||||
if (e == null) {
|
||||
return future;
|
||||
}
|
||||
|
||||
LOG.warning("Failed to fetch versions list and try to fetch from other source", e);
|
||||
return refreshAsync(gameVersion, sourceIndex + 1);
|
||||
}).thenCompose(it -> it);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
versions.clear(gameVersion);
|
||||
return refreshAsync(gameVersion, 0);
|
||||
}
|
||||
|
||||
@@ -17,17 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* The remote version list.
|
||||
*
|
||||
* @param <T> The subclass of {@code RemoteVersion}, the type of RemoteVersion.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class VersionList<T extends RemoteVersion> {
|
||||
@@ -48,6 +47,7 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
|
||||
/**
|
||||
* True if the version list that contains the remote versions which depends on the specific game version has been loaded.
|
||||
*
|
||||
* @param gameVersion the remote version depends on
|
||||
*/
|
||||
public boolean isLoaded(String gameVersion) {
|
||||
@@ -61,44 +61,36 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
/**
|
||||
* @return the task to reload the remote version list.
|
||||
*/
|
||||
public abstract CompletableFuture<?> refreshAsync();
|
||||
public abstract Task<?> refreshAsync();
|
||||
|
||||
/**
|
||||
* @param gameVersion the remote version depends on
|
||||
* @return the task to reload the remote version list.
|
||||
*/
|
||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
return refreshAsync();
|
||||
}
|
||||
|
||||
public CompletableFuture<?> loadAsync() {
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused -> {
|
||||
lock.readLock().lock();
|
||||
boolean loaded;
|
||||
|
||||
try {
|
||||
loaded = isLoaded();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
return loaded ? CompletableFuture.completedFuture(null) : refreshAsync();
|
||||
});
|
||||
public Task<?> loadAsync() {
|
||||
return Task.composeAsync(() -> {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return isLoaded() ? null : refreshAsync();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<?> loadAsync(String gameVersion) {
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused -> {
|
||||
lock.readLock().lock();
|
||||
boolean loaded;
|
||||
|
||||
try {
|
||||
loaded = isLoaded(gameVersion);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
return loaded ? CompletableFuture.completedFuture(null) : refreshAsync(gameVersion);
|
||||
});
|
||||
public Task<?> loadAsync(String gameVersion) {
|
||||
return Task.composeAsync(() -> {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return isLoaded(gameVersion) ? null : refreshAsync(gameVersion);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Collection<T> getVersionsImpl(String gameVersion) {
|
||||
@@ -123,7 +115,7 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
/**
|
||||
* Get the specific remote version.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that remote versions belong to
|
||||
* @param gameVersion the Minecraft version that remote versions belong to
|
||||
* @param remoteVersion the version of the remote version.
|
||||
* @return the specific remote version, null if it is not found.
|
||||
*/
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -59,8 +59,8 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
new URL(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar").toFile(),
|
||||
URI.create(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,12 +21,10 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
|
||||
public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
||||
|
||||
@@ -42,14 +40,14 @@ public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("P7dR8mSH"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -26,10 +27,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
|
||||
public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
||||
@@ -45,8 +44,8 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
List<String> gameVersions = getGameVersions(GAME_META_URL);
|
||||
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
|
||||
|
||||
@@ -60,7 +59,7 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private static final String LOADER_META_URL = "https://meta.fabricmc.net/v2/versions/loader";
|
||||
|
||||
@@ -19,25 +19,25 @@ package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -58,12 +58,12 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> loadAsync() {
|
||||
public Task<?> loadAsync() {
|
||||
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
public Task<?> refreshAsync() {
|
||||
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
||||
}
|
||||
|
||||
@@ -83,11 +83,10 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
String lookupVersion = toLookupVersion(gameVersion);
|
||||
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + lookupVersion).getJson(listTypeOf(ForgeVersion.class))))
|
||||
return new GetTask(URI.create(apiRoot + "/forge/minecraft/" + lookupVersion)).thenGetJsonAsync(listTypeOf(ForgeVersion.class))
|
||||
.thenAcceptAsync(forgeVersions -> {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
|
||||
@@ -69,7 +69,7 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
|
||||
dependent = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
||||
installer.toFile(), null);
|
||||
installer, null);
|
||||
dependent.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
dependent.setCaching(true);
|
||||
dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
@@ -48,7 +47,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -346,12 +345,12 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
throw new Exception("client_mappings download info not found");
|
||||
}
|
||||
|
||||
List<URL> mappingsUrl = dependencyManager.getDownloadProvider()
|
||||
List<URI> mappingsUrl = dependencyManager.getDownloadProvider()
|
||||
.injectURLWithCandidates(mappings.getUrl());
|
||||
FileDownloadTask mappingsTask = new FileDownloadTask(
|
||||
var mappingsTask = new FileDownloadTask(
|
||||
mappingsUrl,
|
||||
new File(output),
|
||||
IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
Path.of(output),
|
||||
FileDownloadTask.IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
mappingsTask.setCaching(true);
|
||||
mappingsTask.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
return mappingsTask;
|
||||
|
||||
@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -53,8 +54,8 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return HttpRequest.GET(FORGE_LIST).getJsonAsync(ForgeVersionRoot.class)
|
||||
public Task<?> refreshAsync() {
|
||||
return new GetTask(FORGE_LIST).thenGetJsonAsync(ForgeVersionRoot.class)
|
||||
.thenAcceptAsync(root -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
@@ -95,5 +96,5 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
});
|
||||
}
|
||||
|
||||
public static final String FORGE_LIST = "https://hmcl-dev.github.io/metadata/forge/";
|
||||
public static final URI FORGE_LIST = URI.create("https://hmcl-dev.github.io/metadata/forge/");
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -102,9 +102,9 @@ public final class GameAssetDownloadTask extends Task<Void> {
|
||||
LOG.warning("Unable to calc hash value of file " + file, e);
|
||||
}
|
||||
if (download) {
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation());
|
||||
List<URI> uris = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation());
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(urls, file.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
var task = new FileDownloadTask(uris, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory()
|
||||
.resolve("assets").resolve("objects").resolve(assetObject.getLocation()));
|
||||
|
||||
+2
-2
@@ -94,9 +94,9 @@ public final class GameAssetIndexDownloadTask extends Task<Void> {
|
||||
|
||||
// We should not check the hash code of asset index file since this file is not consistent
|
||||
// And Mojang will modify this file anytime. So assetIndex.hash might be outdated.
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
var task = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLWithCandidates(assetIndexInfo.getUrl()),
|
||||
assetIndexFile.toFile(),
|
||||
assetIndexFile,
|
||||
verifyHashCode ? new FileDownloadTask.IntegrityCheck("SHA-1", assetIndexInfo.getSha1()) : null
|
||||
);
|
||||
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
|
||||
@@ -20,11 +20,10 @@ package org.jackhuang.hmcl.download.game;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -54,12 +53,12 @@ public final class GameDownloadTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
File jar = dependencyManager.getGameRepository().getVersionJar(version);
|
||||
Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath();
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
var task = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()),
|
||||
jar,
|
||||
IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()));
|
||||
FileDownloadTask.IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()));
|
||||
task.setCaching(true);
|
||||
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
|
||||
|
||||
@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -52,8 +53,8 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return HttpRequest.GET(downloadProvider.getVersionListURL()).getJsonAsync(GameRemoteVersions.class)
|
||||
public Task<?> refreshAsync() {
|
||||
return new GetTask(URI.create(downloadProvider.getVersionListURL())).thenGetJsonAsync(GameRemoteVersions.class)
|
||||
.thenAcceptAsync(root -> {
|
||||
GameRemoteVersions unlistedVersions = null;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -116,8 +116,8 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
}
|
||||
|
||||
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
||||
task = new FileDownloadTask(urls, jar,
|
||||
List<URI> uris = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
||||
task = new FileDownloadTask(uris, jar.toPath(),
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
task.setCacheRepository(cacheRepository);
|
||||
task.setCaching(true);
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public final class VersionJsonDownloadTask extends Task<String> {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameVersionList = dependencyManager.getVersionList("game");
|
||||
|
||||
dependents.add(Task.fromCompletableFuture(gameVersionList.loadAsync(gameVersion)));
|
||||
dependents.add(gameVersionList.loadAsync(gameVersion));
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* This task is to save the version json.
|
||||
@@ -51,7 +51,8 @@ public final class VersionJsonSaveTask extends Task<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
File json = repository.getVersionJson(version.getId()).getAbsoluteFile();
|
||||
FileUtils.writeText(json, JsonUtils.GSON.toJson(version));
|
||||
Path json = repository.getVersionJson(version.getId()).toPath().toAbsolutePath();
|
||||
Files.createDirectories(json.getParent());
|
||||
JsonUtils.writeToJsonFile(json, version);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -103,7 +103,7 @@ public final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Re
|
||||
if (file.getDownloads().containsKey("lzma")) {
|
||||
DownloadInfo download = file.getDownloads().get("lzma");
|
||||
File tempFile = target.resolve(entry.getKey() + ".lzma").toFile();
|
||||
FileDownloadTask task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), tempFile, new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), tempFile.toPath(), new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
task.setName(entry.getKey());
|
||||
dependencies.add(task.thenRunAsync(() -> {
|
||||
Path decompressed = target.resolve(entry.getKey() + ".tmp");
|
||||
@@ -121,7 +121,7 @@ public final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Re
|
||||
}));
|
||||
} else if (file.getDownloads().containsKey("raw")) {
|
||||
DownloadInfo download = file.getDownloads().get("raw");
|
||||
FileDownloadTask task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), dest.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), dest, new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
task.setName(entry.getKey());
|
||||
if (file.isExecutable()) {
|
||||
dependencies.add(task.thenRunAsync(() -> dest.toFile().setExecutable(true)));
|
||||
|
||||
+8
-10
@@ -20,12 +20,12 @@ package org.jackhuang.hmcl.download.liteloader;
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -54,17 +54,15 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
public Task<?> refreshAsync() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
||||
return HttpRequest.GET(
|
||||
downloadProvider.injectURL("https://bmclapi2.bangbang93.com/liteloader/list"), Pair.pair("mcversion", gameVersion)
|
||||
)
|
||||
.getJsonAsync(LiteLoaderBMCLVersion.class)
|
||||
.thenAccept(v -> {
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
return new GetTask(NetworkUtils.withQuery(downloadProvider.injectURLWithCandidates("https://bmclapi2.bangbang93.com/liteloader/list"), Map.of("mcversion", gameVersion)))
|
||||
.thenGetJsonAsync(LiteLoaderBMCLVersion.class)
|
||||
.thenAcceptAsync(v -> {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
versions.clear();
|
||||
|
||||
+7
-4
@@ -20,16 +20,18 @@ package org.jackhuang.hmcl.download.liteloader;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -50,8 +52,9 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
||||
public static final String LITELOADER_LIST = "https://dl.liteloader.com/versions/versions.json";
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
||||
return HttpRequest.GET(downloadProvider.injectURL(LITELOADER_LIST)).getJsonAsync(LiteLoaderVersionsRoot.class)
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
return new GetTask(URI.create(downloadProvider.injectURL(LITELOADER_LIST)))
|
||||
.thenGetJsonAsync(LiteLoaderVersionsRoot.class)
|
||||
.thenAcceptAsync(root -> {
|
||||
LiteLoaderGameVersions versions = root.getVersions().get(gameVersion);
|
||||
if (versions == null) {
|
||||
@@ -85,7 +88,7 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
public Task<?> refreshAsync() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
+7
-8
@@ -20,15 +20,15 @@ package org.jackhuang.hmcl.download.neoforge;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
|
||||
public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVersion> {
|
||||
@@ -47,12 +47,12 @@ public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVer
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> loadAsync() {
|
||||
public Task<?> loadAsync() {
|
||||
throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
public Task<?> refreshAsync() {
|
||||
throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list.");
|
||||
}
|
||||
|
||||
@@ -65,9 +65,8 @@ public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVer
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
||||
return CompletableFuture.completedFuture((Void) null)
|
||||
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).getJson(listTypeOf(NeoForgeVersion.class))))
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
return new GetTask(URI.create(apiRoot + "/neoforge/list/" + gameVersion)).thenGetJsonAsync(listTypeOf(NeoForgeVersion.class))
|
||||
.thenAcceptAsync(neoForgeVersions -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public final class NeoForgeInstallTask extends Task<Version> {
|
||||
|
||||
dependent = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()),
|
||||
installer.toFile(), null
|
||||
installer, null
|
||||
);
|
||||
dependent.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
dependent.setCaching(true);
|
||||
|
||||
+10
-10
@@ -2,14 +2,14 @@ package org.jackhuang.hmcl.download.neoforge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemoteVersion> {
|
||||
@@ -37,17 +37,17 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.supplyAsync(wrap(() -> new OfficialAPIResult[]{
|
||||
HttpRequest.GET(downloadProvider.injectURL(OLD_URL)).getJson(OfficialAPIResult.class),
|
||||
HttpRequest.GET(downloadProvider.injectURL(META_URL)).getJson(OfficialAPIResult.class)
|
||||
})).thenAccept(results -> {
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.allOf(
|
||||
new GetTask(URI.create(downloadProvider.injectURL(OLD_URL))).thenGetJsonAsync(OfficialAPIResult.class),
|
||||
new GetTask(URI.create(downloadProvider.injectURL(META_URL))).thenGetJsonAsync(OfficialAPIResult.class)
|
||||
).thenAcceptAsync(results -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
versions.clear();
|
||||
|
||||
for (String version : results[0].versions) {
|
||||
for (String version : results.get(0).versions) {
|
||||
versions.put("1.20.1", new NeoForgeRemoteVersion(
|
||||
"1.20.1", NeoForgeRemoteVersion.normalize(version),
|
||||
Collections.singletonList(
|
||||
@@ -56,7 +56,7 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
|
||||
));
|
||||
}
|
||||
|
||||
for (String version : results[1].versions) {
|
||||
for (String version : results.get(1).versions) {
|
||||
String mcVersion;
|
||||
|
||||
try {
|
||||
|
||||
+6
-7
@@ -26,7 +26,6 @@ import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -44,7 +43,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -342,12 +341,12 @@ public class NeoForgeOldInstallTask extends Task<Version> {
|
||||
throw new Exception("client_mappings download info not found");
|
||||
}
|
||||
|
||||
List<URL> mappingsUrl = dependencyManager.getDownloadProvider()
|
||||
List<URI> mappingsUri = dependencyManager.getDownloadProvider()
|
||||
.injectURLWithCandidates(mappings.getUrl());
|
||||
FileDownloadTask mappingsTask = new FileDownloadTask(
|
||||
mappingsUrl,
|
||||
new File(output),
|
||||
IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
var mappingsTask = new FileDownloadTask(
|
||||
mappingsUri,
|
||||
Path.of(output),
|
||||
FileDownloadTask.IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
mappingsTask.setCaching(true);
|
||||
mappingsTask.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
return mappingsTask;
|
||||
|
||||
+5
-4
@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.download.optifine;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
|
||||
@@ -71,8 +72,8 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return HttpRequest.GET(apiRoot + "/optifine/versionlist").getJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {
|
||||
public Task<?> refreshAsync() {
|
||||
return new GetTask(URI.create(apiRoot + "/optifine/versionlist")).thenGetJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
|
||||
+2
-2
@@ -96,9 +96,9 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
dest = Files.createTempFile("optifine-installer", ".jar");
|
||||
|
||||
if (installer == null) {
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
var task = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
||||
dest.toFile(), null);
|
||||
dest, null);
|
||||
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependents.add(task);
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -59,8 +59,8 @@ public final class QuiltAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
new URL(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar").toFile(),
|
||||
URI.create(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,12 +21,10 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
|
||||
public class QuiltAPIVersionList extends VersionList<QuiltAPIRemoteVersion> {
|
||||
|
||||
@@ -42,14 +40,14 @@ public class QuiltAPIVersionList extends VersionList<QuiltAPIRemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("qsl"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new QuiltAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download.quilt;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -26,10 +27,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
|
||||
public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
||||
@@ -45,8 +44,8 @@ public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
List<String> gameVersions = getGameVersions(GAME_META_URL);
|
||||
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
|
||||
|
||||
@@ -60,7 +59,7 @@ public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private static final String LOADER_META_URL = "https://meta.quiltmc.org/v3/versions/loader";
|
||||
|
||||
@@ -219,8 +219,9 @@ public class DefaultGameRepository implements GameRepository {
|
||||
// fix inheritsFrom of versions that inherits from version [from].
|
||||
for (Version version : getVersions()) {
|
||||
if (from.equals(version.getInheritsFrom())) {
|
||||
File json = getVersionJson(version.getId()).getAbsoluteFile();
|
||||
FileUtils.writeText(json, JsonUtils.GSON.toJson(version.setInheritsFrom(to)));
|
||||
Path targetPath = getVersionJson(version.getId()).toPath();
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
JsonUtils.writeToJsonFile(targetPath, version.setInheritsFrom(to));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -384,7 +385,7 @@ public class DefaultGameRepository implements GameRepository {
|
||||
@Override
|
||||
public AssetIndex getAssetIndex(String version, String assetId) throws IOException {
|
||||
try {
|
||||
return Objects.requireNonNull(JsonUtils.GSON.fromJson(Files.readString(getIndexFile(version, assetId)), AssetIndex.class));
|
||||
return Objects.requireNonNull(JsonUtils.fromJsonFile(getIndexFile(version, assetId), AssetIndex.class));
|
||||
} catch (JsonParseException | NullPointerException e) {
|
||||
throw new IOException("Asset index file malformed", e);
|
||||
}
|
||||
@@ -445,8 +446,7 @@ public class DefaultGameRepository implements GameRepository {
|
||||
if (!Files.isRegularFile(indexFile))
|
||||
return assetsDir;
|
||||
|
||||
String assetIndexContent = Files.readString(indexFile);
|
||||
AssetIndex index = JsonUtils.GSON.fromJson(assetIndexContent, AssetIndex.class);
|
||||
AssetIndex index = JsonUtils.fromJsonFile(indexFile, AssetIndex.class);
|
||||
|
||||
if (index == null)
|
||||
return assetsDir;
|
||||
@@ -455,7 +455,7 @@ public class DefaultGameRepository implements GameRepository {
|
||||
Path resourcesDir = getRunDirectory(version).toPath().resolve("resources");
|
||||
|
||||
int cnt = 0;
|
||||
int tot = index.getObjects().entrySet().size();
|
||||
int tot = index.getObjects().size();
|
||||
for (Map.Entry<String, AssetObject> entry : index.getObjects().entrySet()) {
|
||||
Path target = virtualRoot.resolve(entry.getKey());
|
||||
Path original = getAssetObject(version, assetsDir, entry.getValue());
|
||||
@@ -512,7 +512,7 @@ public class DefaultGameRepository implements GameRepository {
|
||||
if (!hasVersion(version)) throw new VersionNotFoundException(version);
|
||||
File file = getModpackConfiguration(version);
|
||||
if (!file.exists()) return null;
|
||||
return JsonUtils.GSON.fromJson(Files.readString(file.toPath()), ModpackConfiguration.class);
|
||||
return JsonUtils.fromJsonFile(file.toPath(), ModpackConfiguration.class);
|
||||
}
|
||||
|
||||
public boolean isModpack(String version) {
|
||||
|
||||
@@ -53,7 +53,8 @@ public class Library implements Comparable<Library>, Validation {
|
||||
"",
|
||||
Architecture.SYSTEM_ARCH.name().toLowerCase(Locale.ROOT),
|
||||
Architecture.SYSTEM_ARCH.getBits().getBit()
|
||||
}, variants = {"", "native", "natives"};
|
||||
};
|
||||
String[] variants = {"", "native", "natives"};
|
||||
|
||||
POSSIBLE_NATIVE_DESCRIPTORS = new String[keys.length * variants.length];
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@@ -132,7 +132,7 @@ public class Datapack {
|
||||
try {
|
||||
PackMcMeta pack = JsonUtils.fromNonNullJson(Files.readString(mcmeta), PackMcMeta.class);
|
||||
Platform.runLater(() -> info.add(new Pack(path, FileUtils.getNameWithoutExtension(path), pack.getPackInfo().getDescription(), this)));
|
||||
} catch (IOException | JsonParseException e) {
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to read datapack " + path, e);
|
||||
}
|
||||
} else {
|
||||
@@ -144,7 +144,7 @@ public class Datapack {
|
||||
public void loadFromDir() {
|
||||
try {
|
||||
loadFromDir(path);
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to read datapacks " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,9 @@ public final class MinecraftInstanceTask<T> extends Task<ModpackConfiguration<T>
|
||||
}
|
||||
|
||||
ModpackConfiguration<T> configuration = new ModpackConfiguration<>(manifest, type, name, version, overrides);
|
||||
FileUtils.writeText(jsonFile, JsonUtils.GSON.toJson(configuration));
|
||||
Path jsonPath = jsonFile.toPath();
|
||||
Files.createDirectories(jsonPath.getParent());
|
||||
JsonUtils.writeToJsonFile(jsonPath, configuration);
|
||||
setResult(configuration);
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,10 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -88,7 +86,7 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
try {
|
||||
File manifestFile = new File(repository.getVersionRoot(version), "manifest.json");
|
||||
if (manifestFile.exists())
|
||||
this.manifest = JsonUtils.GSON.fromJson(Files.readString(manifestFile.toPath()), CurseManifest.class);
|
||||
this.manifest = JsonUtils.fromJsonFile(manifestFile.toPath(), CurseManifest.class);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Unable to read CurseForge modpack manifest.json", e);
|
||||
}
|
||||
@@ -137,7 +135,7 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
FileUtils.writeText(new File(root, "manifest.json"), JsonUtils.GSON.toJson(newManifest));
|
||||
JsonUtils.writeToJsonFile(root.toPath().resolve("manifest.json"), newManifest);
|
||||
|
||||
File versionRoot = repository.getVersionRoot(modManager.getVersion());
|
||||
File resourcePacksRoot = new File(versionRoot, "resourcepacks"), shaderPacksRoot = new File(versionRoot, "shaderpacks");
|
||||
@@ -152,7 +150,7 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(f.getUrl(), path);
|
||||
var task = new FileDownloadTask(f.getUrl(), path.toPath());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
return Stream.of(task.withCounter("hmcl.modpack.download"));
|
||||
|
||||
@@ -25,11 +25,11 @@ import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -60,7 +60,6 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
* @param zipFile the CurseForge modpack file.
|
||||
* @param manifest The manifest content of given CurseForge modpack.
|
||||
* @param name the new version name
|
||||
* @see CurseManifest#readCurseForgeModpackManifest
|
||||
*/
|
||||
public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, CurseManifest manifest, String name) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
@@ -99,7 +98,7 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
ModpackConfiguration<CurseManifest> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(Files.readString(json.toPath()), ModpackConfiguration.typeOf(CurseManifest.class));
|
||||
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(CurseManifest.class));
|
||||
|
||||
if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version.");
|
||||
@@ -137,7 +136,8 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
File root = repository.getVersionRoot(name);
|
||||
FileUtils.writeText(new File(root, "manifest.json"), JsonUtils.GSON.toJson(manifest));
|
||||
Path root = repository.getVersionRoot(name).toPath();
|
||||
Files.createDirectories(root);
|
||||
JsonUtils.writeToJsonFile(root.resolve("manifest.json"), manifest);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -84,15 +84,15 @@ public final class CurseManifestFile implements Validation {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public URL getUrl() {
|
||||
public URI getUrl() {
|
||||
if (url == null) {
|
||||
if (fileName != null) {
|
||||
return NetworkUtils.toURL(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName)));
|
||||
return URI.create(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName)));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return NetworkUtils.toURL(NetworkUtils.encodeLocation(url));
|
||||
return URI.create(NetworkUtils.encodeLocation(url));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -28,7 +28,6 @@ import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -36,7 +35,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -102,7 +101,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
throw new CustomException();
|
||||
}
|
||||
})).thenComposeAsync(wrap(unused1 -> {
|
||||
return executor.one(new GetTask(new URL(manifest.getFileApi() + "/manifest.json")));
|
||||
return executor.one(new GetTask(URI.create(manifest.getFileApi() + "/manifest.json")));
|
||||
})).thenComposeAsync(wrap(remoteManifestJson -> {
|
||||
McbbsModpackManifest remoteManifest;
|
||||
// We needs to update modpack from online server.
|
||||
@@ -113,6 +112,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
}
|
||||
|
||||
Path rootPath = repository.getVersionRoot(version).toPath();
|
||||
Files.createDirectories(rootPath);
|
||||
|
||||
Map<McbbsModpackManifest.File, McbbsModpackManifest.File> localFiles = manifest.getFiles().stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
|
||||
|
||||
@@ -173,15 +173,15 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
manifest = remoteManifest.setFiles(newFiles);
|
||||
return executor.all(tasks.stream().filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
})).thenAcceptAsync(wrapConsumer(unused1 -> {
|
||||
File manifestFile = repository.getModpackConfiguration(version);
|
||||
FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(
|
||||
Path manifestFile = repository.getModpackConfiguration(version).toPath();
|
||||
JsonUtils.writeToJsonFile(manifestFile,
|
||||
new ModpackConfiguration<>(manifest, this.configuration.getType(), this.manifest.getName(), this.manifest.getVersion(),
|
||||
this.manifest.getFiles().stream()
|
||||
.flatMap(file -> file instanceof McbbsModpackManifest.AddonFile
|
||||
? Stream.of((McbbsModpackManifest.AddonFile) file)
|
||||
: Stream.empty())
|
||||
.map(file -> new ModpackConfiguration.FileInformation(file.getPath(), file.getHash()))
|
||||
.collect(Collectors.toList()))));
|
||||
.collect(Collectors.toList())));
|
||||
})));
|
||||
}).thenComposeAsync(unused -> {
|
||||
AtomicBoolean allNameKnown = new AtomicBoolean(true);
|
||||
@@ -204,7 +204,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID())));
|
||||
String result = NetworkUtils.doGet(URI.create(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID())));
|
||||
CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
||||
return file.withFileName(mod.getFileNameOnDisk()).withURL(mod.getDownloadURL());
|
||||
} catch (FileNotFoundException fof) {
|
||||
@@ -213,7 +213,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
return file;
|
||||
} catch (IOException | JsonParseException e2) {
|
||||
try {
|
||||
String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID())));
|
||||
String result = NetworkUtils.doGet(URI.create(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID())));
|
||||
CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
||||
return file.withFileName(mod.getFileName()).withURL(mod.getDownloadURL());
|
||||
} catch (FileNotFoundException fof) {
|
||||
@@ -240,14 +240,14 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
|
||||
manifest = newManifest;
|
||||
configuration = configuration.setManifest(newManifest);
|
||||
FileUtils.writeText(configurationFile, JsonUtils.GSON.toJson(configuration));
|
||||
JsonUtils.writeToJsonFile(configurationFile.toPath(), configuration);
|
||||
|
||||
for (McbbsModpackManifest.File file : newManifest.getFiles())
|
||||
if (file instanceof McbbsModpackManifest.CurseFile) {
|
||||
McbbsModpackManifest.CurseFile curseFile = (McbbsModpackManifest.CurseFile) file;
|
||||
if (StringUtils.isNotBlank(curseFile.getFileName())) {
|
||||
if (!modManager.hasSimpleMod(curseFile.getFileName())) {
|
||||
FileDownloadTask task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName()).toFile());
|
||||
var task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName()));
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
@@ -297,8 +297,8 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
if (file instanceof McbbsModpackManifest.AddonFile) {
|
||||
McbbsModpackManifest.AddonFile addonFile = (McbbsModpackManifest.AddonFile) file;
|
||||
return new FileDownloadTask(
|
||||
new URL(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(addonFile.getPath())),
|
||||
modManager.getSimpleModPath(addonFile.getPath()).toFile(),
|
||||
URI.create(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(addonFile.getPath())),
|
||||
modManager.getSimpleModPath(addonFile.getPath()),
|
||||
addonFile.getHash() != null ? new FileDownloadTask.IntegrityCheck("SHA-1", addonFile.getHash()) : null);
|
||||
} else if (file instanceof McbbsModpackManifest.CurseFile) {
|
||||
// we download it later.
|
||||
|
||||
+1
-2
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -79,7 +78,7 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
|
||||
ModpackConfiguration<McbbsModpackManifest> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(Files.readString(json.toPath()), ModpackConfiguration.typeOf(McbbsModpackManifest.class));
|
||||
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(McbbsModpackManifest.class));
|
||||
|
||||
if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version.");
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -329,9 +329,9 @@ public class McbbsModpackManifest implements ModpackManifest, Validation {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return url == null ? NetworkUtils.toURL("https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file")
|
||||
: NetworkUtils.toURL(NetworkUtils.encodeLocation(url));
|
||||
public URI getUrl() {
|
||||
return url == null ? URI.create("https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file")
|
||||
: URI.create(NetworkUtils.encodeLocation(url));
|
||||
}
|
||||
|
||||
public CurseFile withFileName(String fileName) {
|
||||
|
||||
+2
-3
@@ -27,7 +27,6 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -62,10 +61,10 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
|
||||
repository.removeVersionFromDisk(name);
|
||||
});
|
||||
|
||||
ModpackConfiguration<McbbsModpackManifest> config = null;
|
||||
ModpackConfiguration<McbbsModpackManifest> config;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(Files.readString(json.toPath()), ModpackConfiguration.typeOf(McbbsModpackManifest.class));
|
||||
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(McbbsModpackManifest.class));
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version.");
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class ForgeOldModMetadata {
|
||||
Path mcmod = fs.getPath("mcmod.info");
|
||||
if (Files.notExists(mcmod))
|
||||
throw new IOException("File " + modFile + " is not a Forge mod.");
|
||||
List<ForgeOldModMetadata> modList = JsonUtils.GSON.fromJson(Files.readString(mcmod), listTypeOf(ForgeOldModMetadata.class));
|
||||
List<ForgeOldModMetadata> modList = JsonUtils.fromJsonFile(mcmod, listTypeOf(ForgeOldModMetadata.class));
|
||||
if (modList == null || modList.isEmpty())
|
||||
throw new IOException("Mod " + modFile + " `mcmod.info` is malformed..");
|
||||
ForgeOldModMetadata metadata = modList.get(0);
|
||||
|
||||
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user