feat(java): download java 8/16 when auto selected java not found.

This commit is contained in:
huanghongxun 2021-10-18 23:38:57 +08:00
parent 71c23df971
commit a4f22671c6
11 changed files with 114 additions and 78 deletions

View File

@ -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<JavaVersion> 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<Void> downloadJava(GameJavaVersion javaVersion, Profile profile) {
CompletableFuture<Void> future = new CompletableFuture<>();
private static CompletableFuture<JavaVersion> downloadJava(String gameVersion, GameJavaVersion javaVersion, Profile profile) {
CompletableFuture<JavaVersion> 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<JavaVersion> downloadJavaImpl(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
CompletableFuture<JavaVersion> 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;
}

View File

@ -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);
}

View File

@ -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");

View File

@ -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

View File

@ -292,6 +292,7 @@ download.javafx.prepare=準備開始下載
exception.access_denied=因為無法訪問文件 %sHMCL 沒有對該文件的訪問權限,或者該文件被其他程序打開。\n\
\n\
對於 Windows 用戶,你還可以嘗試通過資源監視器查看是否有程序占用了該文件,如果是,你可以關閉占用此文件相關程序,或者重啟電腦再試。
exception.artifact_malformed=下載的文件正確,無法通過校驗。
extension.bat=Windows 指令碼
extension.mod=模組檔案

View File

@ -292,6 +292,7 @@ download.javafx.prepare=准备开始下载
exception.access_denied=因为无法访问文件 %sHMCL 没有对该文件的访问权限,或者该文件被其他程序打开。\n\
访访\n\
对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,你可以关闭占用此文件相关程序,或者重启电脑再试。
exception.artifact_malformed=下载的文件正确,无法通过校验。
extension.bat=Windows 脚本
extension.mod=模组文件

View File

@ -101,7 +101,7 @@ public class JavaDownloadTask extends Task<Void> {
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")) {

View File

@ -21,23 +21,25 @@ public final class JavaRepository {
private JavaRepository() {
}
public static Task<?> downloadJava(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
public static Task<JavaVersion> downloadJava(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
return new JavaDownloadTask(javaVersion, getJavaStoragePath(), downloadProvider)
.thenRunAsync(() -> {
Optional<String> 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 {

View File

@ -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);
}

View File

@ -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;

View File

@ -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 <a href="http://maven.apache.org/pom.html#Version_Order_Specification">Specification</a>
*/
public class VersionNumber implements Comparable<VersionNumber> {