diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 778460ae3..000a0e024 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -17,13 +17,13 @@ */ package org.jackhuang.hmcl.game; -import com.jfoenix.controls.JFXButton; import javafx.application.Platform; import javafx.stage.Stage; import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException; import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask; import org.jackhuang.hmcl.download.game.GameVerificationFixTask; @@ -38,16 +38,14 @@ import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackCompletionTask; import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask; import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask; import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask; +import org.jackhuang.hmcl.setting.DownloadProviders; import org.jackhuang.hmcl.setting.LauncherVisibility; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.*; -import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; -import org.jackhuang.hmcl.ui.construct.MessageDialogPane; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; -import org.jackhuang.hmcl.ui.construct.PromptDialogPane; -import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.ResponseCodeException; @@ -337,9 +335,28 @@ public final class LauncherHelper { Runnable continueAction = () -> future.complete(JavaVersion.fromCurrentEnvironment()); if (setting.isJavaAutoSelected()) { -// JavaVersionConstraint.VersionRange range = JavaVersionConstraint.findSuitableJavaVersionRange(gameVersion, version); - // TODO: download java 16 if necessary! - Controllers.dialog(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction); + JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(gameVersion, version); + GameJavaVersion targetJavaVersion; + + if (range.getMandatory().contains(VersionNumber.asVersion("16"))) { + targetJavaVersion = GameJavaVersion.JAVA_16; + } else if (range.getMandatory().contains(VersionNumber.asVersion("1.8.0_51"))) { + targetJavaVersion = GameJavaVersion.JAVA_8; + } else { + targetJavaVersion = null; + } + + if (targetJavaVersion != null) { + downloadJava(gameVersion.toString(), targetJavaVersion, profile) + .thenAcceptAsync(downloadedJavaVersion -> { + future.complete(downloadedJavaVersion); + }) + .exceptionally(throwable -> { + LOG.log(Level.WARNING, "Failed to download java", throwable); + Controllers.dialog(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction); + return null; + }); + } } else { Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, continueAction); @@ -391,47 +408,15 @@ public final class LauncherHelper { } else { switch (violatedMandatoryConstraint) { case GAME_JSON: - MessageDialogPane dialog = new MessageDialogPane( - i18n("launch.advice.require_newer_java_version", - gameVersion.toString(), - version.getJavaVersion().getMajorVersion()), - i18n("message.warning"), - MessageType.QUESTION); - - JFXButton linkButton = new JFXButton(i18n("download.external_link")); - linkButton.setOnAction(e -> FXUtils.openLink("https://adoptium.net/?variant=openjdk17")); - linkButton.getStyleClass().add("dialog-accept"); - dialog.addButton(linkButton); - - JFXButton yesButton = new JFXButton(i18n("button.ok")); - yesButton.setOnAction(event -> { - downloadJava(version.getJavaVersion(), profile) - .thenAcceptAsync(x -> { - try { - Optional newAcceptableJava = JavaVersion.getJavas().stream() - .filter(newJava -> newJava.getParsedVersion() >= version.getJavaVersion().getMajorVersion()) - .max(Comparator.comparing(JavaVersion::getVersionNumber)); - if (newAcceptableJava.isPresent()) { - setting.setJavaVersion(newAcceptableJava.get()); - future.complete(newAcceptableJava.get()); - return; - } - } catch (InterruptedException e) { - LOG.log(Level.SEVERE, "Cannot list javas", e); - } - future.complete(javaVersion); - }, Platform::runLater) - .exceptionally(Lang.handleUncaught); - }); - yesButton.getStyleClass().add("dialog-accept"); - dialog.addButton(yesButton); - - JFXButton noButton = new JFXButton(i18n("button.cancel")); - noButton.getStyleClass().add("dialog-cancel"); - dialog.addButton(noButton); - dialog.setCancelButton(noButton); - - Controllers.dialog(dialog); + downloadJava(gameVersion.toString(), version.getJavaVersion(), profile) + .thenAcceptAsync(downloadedJavaVersion -> { + setting.setJavaVersion(downloadedJavaVersion); + future.complete(downloadedJavaVersion); + }) + .exceptionally(throwable -> { + LOG.log(Level.WARNING, "Failed to download java", throwable); + return null; + }); return Task.fromCompletableFuture(future); case VANILLA_JAVA_16: Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 16), i18n("message.warning"), () -> { @@ -539,33 +524,64 @@ public final class LauncherHelper { }).withStage("launch.state.java"); } - private static CompletableFuture downloadJava(GameJavaVersion javaVersion, Profile profile) { - CompletableFuture future = new CompletableFuture<>(); + private static CompletableFuture downloadJava(String gameVersion, GameJavaVersion javaVersion, Profile profile) { + CompletableFuture future = new CompletableFuture<>(); + + JFXHyperlink link = new JFXHyperlink(i18n("download.external_link")); + link.setOnAction(e -> FXUtils.openLink("https://adoptium.net/?variant=openjdk17")); + + Controllers.dialog(new MessageDialogPane.Builder( + i18n("launch.advice.require_newer_java_version", + gameVersion, + javaVersion.getMajorVersion()), + i18n("message.warning"), + MessageType.QUESTION) + .addAction(link) + .yesOrNo(() -> { + downloadJavaImpl(javaVersion, profile.getDependency().getDownloadProvider()) + .thenAcceptAsync(downloadedJava -> { + future.complete(downloadedJava); + }) + .exceptionally(throwable -> { + LOG.log(Level.WARNING, "Failed to download java", throwable); + Controllers.dialog(DownloadProviders.localizeErrorMessage(throwable), i18n("download.failed")); + future.completeExceptionally(new CancellationException()); + return null; + }); + }, () -> { + future.completeExceptionally(new CancellationException()); + }).build()); + + return future; + } + + /** + * Directly start java downloading. + * + * @param javaVersion target Java version + * @param downloadProvider download provider + * @return JavaVersion, null if we failed to download java, failed if an error occurred when downloading. + */ + private static CompletableFuture downloadJavaImpl(GameJavaVersion javaVersion, DownloadProvider downloadProvider) { + CompletableFuture future = new CompletableFuture<>(); TaskExecutorDialogPane javaDownloadingPane = new TaskExecutorDialogPane(it -> { }); - TaskExecutor executor = JavaRepository.downloadJava(javaVersion, - profile.getDependency().getDownloadProvider()).executor(false); - executor.addTaskListener(new TaskListener() { - @Override - public void onStop(boolean success, TaskExecutor executor) { - super.onStop(success, executor); - Platform.runLater(() -> { - if (!success) { - future.completeExceptionally(Optional.ofNullable(executor.getException()).orElseGet(InterruptedException::new)); + TaskExecutor executor = JavaRepository.downloadJava(javaVersion, downloadProvider) + .whenComplete(Schedulers.javafx(), (downloadedJava, exception) -> { + if (exception != null) { + future.completeExceptionally(exception); } else { - future.complete(null); + future.complete(downloadedJava); } - }); - } - }); + }) + .executor(false); javaDownloadingPane.setExecutor(executor, true); Controllers.dialog(javaDownloadingPane); executor.start(); - return future; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java index 80255da05..0b99c84bf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java @@ -137,7 +137,7 @@ public final class DownloadProviders { return config().isAutoChooseDownloadType() ? currentDownloadProvider : fileDownloadProvider; } - public static String localizeErrorMessage(Exception exception) { + public static String localizeErrorMessage(Throwable exception) { if (exception instanceof DownloadException) { URL url = ((DownloadException) exception).getUrl(); if (exception.getCause() instanceof SocketTimeoutException) { @@ -153,9 +153,13 @@ public final class DownloadProviders { return i18n("download.code.404", url); } else if (exception.getCause() instanceof AccessDeniedException) { return i18n("install.failed.downloading.detail", url) + "\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"); } else { return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()); } + } else if (exception instanceof ArtifactMalformedException) { + return i18n("exception.artifact_malformed"); } return StringUtils.getStackTrace(exception); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index f56fa460f..f93b17c33 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.controls.JFXButton; import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.Node; import javafx.scene.control.ButtonBase; import javafx.scene.control.Label; import javafx.scene.layout.HBox; @@ -99,7 +100,7 @@ public final class MessageDialogPane extends StackPane { }); } - public void addButton(ButtonBase btn) { + public void addButton(Node btn) { btn.addEventHandler(ActionEvent.ACTION, e -> fireEvent(new DialogCloseEvent())); actions.getChildren().add(btn); } @@ -119,6 +120,11 @@ public final class MessageDialogPane extends StackPane { this.dialog = new MessageDialogPane(text, title, type); } + public Builder addAction(Node actionNode) { + dialog.addButton(actionNode); + return this; + } + public Builder ok(Runnable ok) { JFXButton btnOk = new JFXButton(i18n("button.ok")); btnOk.getStyleClass().add("dialog-accept"); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index e4a167c89..dcb007e35 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -292,6 +292,7 @@ download.javafx.prepare=Ready to download exception.access_denied=It's denied by operating system to access file %s. Maybe we don't have permission to access this file, or this file has already been opened by other program.\n\ Please check if current operating system user has permission to access that file.\n\ For Windows users, you can also try the Resource Monitor, find if some programs is holding the file, try to close related program, or restart your computer. +exception.artifact_malformed=The file cannot pass verification. extension.bat=Windows Bat file extension.mod=Mod file diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 0244b2d8a..be358c7b9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -292,6 +292,7 @@ download.javafx.prepare=準備開始下載 exception.access_denied=因為無法訪問文件 %s,HMCL 沒有對該文件的訪問權限,或者該文件被其他程序打開。\n\ 請你檢查當前操作系統帳戶是否能訪問該文件,比如非管理員用戶可能不能訪問其他帳戶的個人文件夾內的文件。\n\ 對於 Windows 用戶,你還可以嘗試通過資源監視器查看是否有程序占用了該文件,如果是,你可以關閉占用此文件相關程序,或者重啟電腦再試。 +exception.artifact_malformed=下載的文件正確,無法通過校驗。 extension.bat=Windows 指令碼 extension.mod=模組檔案 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index cc6935789..7ae197859 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -292,6 +292,7 @@ download.javafx.prepare=准备开始下载 exception.access_denied=因为无法访问文件 %s,HMCL 没有对该文件的访问权限,或者该文件被其他程序打开。\n\ 请你检查当前操作系统帐户是否能访问该文件,比如非管理员用户可能不能访问其他帐户的个人文件夹内的文件。\n\ 对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,你可以关闭占用此文件相关程序,或者重启电脑再试。 +exception.artifact_malformed=下载的文件正确,无法通过校验。 extension.bat=Windows 脚本 extension.mod=模组文件 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java index 4d4e07c7b..d5325bea3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java @@ -101,7 +101,7 @@ public class JavaDownloadTask extends Task { try (LZMAInputStream input = new LZMAInputStream(new FileInputStream(tempFile))) { Files.copy(input, dest); } catch (IOException e) { - throw new ArtifactMalformedException("File " + entry.getKey() + " is malformed"); + throw new ArtifactMalformedException("File " + entry.getKey() + " is malformed", e); } })); } else if (file.getDownloads().containsKey("raw")) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRepository.java index 45116bf6b..e2e323d9d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRepository.java @@ -21,23 +21,25 @@ public final class JavaRepository { private JavaRepository() { } - public static Task downloadJava(GameJavaVersion javaVersion, DownloadProvider downloadProvider) { + public static Task downloadJava(GameJavaVersion javaVersion, DownloadProvider downloadProvider) { return new JavaDownloadTask(javaVersion, getJavaStoragePath(), downloadProvider) - .thenRunAsync(() -> { - Optional platform = getSystemJavaPlatform(); - if (platform.isPresent()) { - addJava(getJavaHome(javaVersion, platform.get())); - } + .thenSupplyAsync(() -> { + String platform = getSystemJavaPlatform().orElseThrow(JavaDownloadTask.UnsupportedPlatformException::new); + return addJava(getJavaHome(javaVersion, platform)); }); } - public static void addJava(Path javaHome) throws InterruptedException, IOException { + public static JavaVersion addJava(Path javaHome) throws InterruptedException, IOException { if (Files.isDirectory(javaHome)) { Path executable = JavaVersion.getExecutable(javaHome); if (Files.isRegularFile(executable)) { - JavaVersion.getJavas().add(JavaVersion.fromExecutable(executable)); + JavaVersion javaVersion = JavaVersion.fromExecutable(executable); + JavaVersion.getJavas().add(javaVersion); + return javaVersion; } } + + throw new IOException("Incorrect java home " + javaHome); } public static void initialize() throws IOException, InterruptedException { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java index ea554bf93..9d735c246 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java @@ -37,4 +37,7 @@ public class GameJavaVersion { public int getMajorVersion() { return majorVersion; } + + public static final GameJavaVersion JAVA_16 = new GameJavaVersion("java-runtime-alpha", 16); + public static final GameJavaVersion JAVA_8 = new GameJavaVersion("jre-legacy", 8); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ChecksumMismatchException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ChecksumMismatchException.java index ee48263ae..473b9f618 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ChecksumMismatchException.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ChecksumMismatchException.java @@ -17,9 +17,9 @@ */ package org.jackhuang.hmcl.util.io; -import java.io.IOException; +import org.jackhuang.hmcl.download.ArtifactMalformedException; -public class ChecksumMismatchException extends IOException { +public class ChecksumMismatchException extends ArtifactMalformedException { private final String algorithm; private final String expectedChecksum; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java index 5be68b15e..6168b7957 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java @@ -25,6 +25,8 @@ import java.util.*; /** * Copied from org.apache.maven.artifact.versioning.ComparableVersion * Apache License 2.0 + * + * Maybe we can migrate to org.jenkins-ci:version-number:1.7? * @see Specification */ public class VersionNumber implements Comparable {