自动开始下载 JavaFX (#1063)

* Automatically start downloading JavaFX

* Reimplement ProgressFrame with JDialog
This commit is contained in:
Glavo 2021-10-13 13:54:24 +08:00 committed by GitHub
parent 2cb70a41fb
commit 3fad76aae1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 76 deletions

View File

@ -99,6 +99,9 @@ public final class Main {
} catch (SelfDependencyPatcher.IncompatibleVersionException e) {
LOG.log(Level.SEVERE, "unable to patch JVM", e);
showErrorAndExit(i18n("fatal.javafx.incompatible"));
} catch (SelfDependencyPatcher.CanceledException e) {
LOG.log(Level.SEVERE, "User cancels downloading JavaFX", e);
System.exit(0);
}
}

View File

@ -49,18 +49,18 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA;
import java.awt.Dialog;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.List;
import javax.swing.*;
@ -202,7 +202,7 @@ public final class SelfDependencyPatcher {
/**
* Patch in any missing dependencies, if any.
*/
public static void patch() throws PatchException, IncompatibleVersionException {
public static void patch() throws PatchException, IncompatibleVersionException, CanceledException {
// Do nothing if JavaFX is detected
try {
try {
@ -233,10 +233,8 @@ public final class SelfDependencyPatcher {
// Download missing dependencies
List<DependencyDescriptor> missingDependencies = checkMissingDependencies();
if (!missingDependencies.isEmpty()) {
final Repository repository = showChooseRepositoryDialog();
try {
fetchDependencies(repository, missingDependencies);
fetchDependencies(missingDependencies);
} catch (IOException e) {
throw new PatchException("Failed to download dependencies", e);
}
@ -273,9 +271,9 @@ public final class SelfDependencyPatcher {
}
}
int res = JOptionPane.showConfirmDialog(null, panel, i18n("repositories.chooser.title"), JOptionPane.YES_NO_OPTION);
int res = JOptionPane.showConfirmDialog(null, panel, i18n("repositories.chooser.title"), JOptionPane.OK_CANCEL_OPTION);
if (res == JOptionPane.YES_OPTION) {
if (res == JOptionPane.OK_OPTION) {
final Enumeration<AbstractButton> buttons = buttonGroup.getElements();
while (buttons.hasMoreElements()) {
final AbstractButton button = buttons.nextElement();
@ -317,28 +315,86 @@ public final class SelfDependencyPatcher {
*
* @throws IOException When the files cannot be fetched or saved.
*/
private static void fetchDependencies(Repository repository, List<DependencyDescriptor> dependencies) throws IOException {
ProgressFrame dialog = new ProgressFrame(i18n("download.javafx"));
dialog.setVisible(true);
int progress = 0;
for (DependencyDescriptor dependency : dependencies) {
int currentProgress = ++progress;
final String url = repository.resolveDependencyURL(dependency);
SwingUtilities.invokeLater(() -> {
dialog.setStatus(url);
dialog.setProgress(currentProgress, dependencies.size() + 1);
});
LOG.info("Downloading " + url);
Files.createDirectories(dependency.localPath().getParent());
try (InputStream is = new URL(url).openStream()) {
Files.copy(is, dependency.localPath(), StandardCopyOption.REPLACE_EXISTING);
}
verifyChecksum(dependency);
private static void fetchDependencies(List<DependencyDescriptor> dependencies) throws IOException {
class BooleanHole {
volatile boolean value = false;
}
dialog.dispose();
boolean isFirstTime = true;
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
Repository repository = Repository.DEFAULT;
int count = 0;
while (true) {
BooleanHole isCancelled = new BooleanHole();
BooleanHole showDetails = new BooleanHole();
ProgressFrame dialog = new ProgressFrame(i18n("download.javafx"));
dialog.setProgressMaximum(dependencies.size() + 1);
dialog.setProgress(count);
dialog.setOnCancel(() -> isCancelled.value = true);
dialog.setOnChangeSource(() -> {
isCancelled.value = true;
showDetails.value = true;
});
dialog.setVisible(true);
try {
if (isFirstTime) {
isFirstTime = false;
try {
//noinspection BusyWait
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
}
Files.createDirectories(DependencyDescriptor.DEPENDENCIES_DIR_PATH);
for (int i = count; i < dependencies.size(); i++) {
if (isCancelled.value) {
throw new CanceledException();
}
DependencyDescriptor dependency = dependencies.get(i);
final String url = repository.resolveDependencyURL(dependency);
SwingUtilities.invokeLater(() -> {
dialog.setCurrent(dependency.module);
dialog.incrementProgress();
});
LOG.info("Downloading " + url);
try (InputStream is = new URL(url).openStream();
OutputStream os = Files.newOutputStream(dependency.localPath())) {
int read;
while ((read = is.read(buffer, 0, IOUtils.DEFAULT_BUFFER_SIZE)) >= 0) {
if (isCancelled.value) {
try {
os.close();
} finally {
Files.deleteIfExists(dependency.localPath());
}
throw new CanceledException();
}
os.write(buffer, 0, read);
}
}
verifyChecksum(dependency);
count++;
}
} catch (CanceledException e) {
dialog.dispose();
if (showDetails.value) {
repository = showChooseRepositoryDialog();
continue;
} else {
throw e;
}
}
dialog.dispose();
return;
}
}
private static List<DependencyDescriptor> checkMissingDependencies() {
@ -383,64 +439,78 @@ public final class SelfDependencyPatcher {
public static class IncompatibleVersionException extends Exception {
}
public static class CanceledException extends RuntimeException {
}
public static class ProgressFrame extends JDialog {
private final JProgressBar progressBar;
private final JLabel progressText;
private final JButton btnChangeSource;
private final JButton btnCancel;
public ProgressFrame(String title) {
super((Dialog) null);
JPanel panel = new JPanel();
setResizable(false);
setTitle(title);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setBounds(100, 100, 600, 150);
setBounds(100, 100, 500, 200);
setContentPane(panel);
setLocationRelativeTo(null);
GridBagLayout gridBagLayout = new GridBagLayout();
gridBagLayout.columnWidths = new int[]{600, 0};
gridBagLayout.rowHeights = new int[]{0, 0, 0, 200};
gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE};
gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 1.0};
panel.setLayout(gridBagLayout);
progressText = new JLabel("");
GridBagConstraints gbc_lblProgressText = new GridBagConstraints();
gbc_lblProgressText.insets = new Insets(10, 0, 5, 0);
gbc_lblProgressText.gridx = 0;
gbc_lblProgressText.gridy = 0;
panel.add(progressText, gbc_lblProgressText);
JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
for (String note : i18n("download.javafx.notes").split("\n")) {
content.add(new JLabel(note));
}
content.add(new JLabel("<html><br/></html>"));
progressText = new JLabel(i18n("download.javafx.prepare"));
content.add(progressText);
progressBar = new JProgressBar();
GridBagConstraints gbc_progressBar = new GridBagConstraints();
gbc_progressBar.insets = new Insets(0, 25, 5, 25);
gbc_progressBar.fill = GridBagConstraints.HORIZONTAL;
gbc_progressBar.gridx = 0;
gbc_progressBar.gridy = 1;
panel.add(progressBar, gbc_progressBar);
content.add(progressBar);
JButton btnCancel = new JButton(i18n("button.cancel"));
btnCancel.addActionListener(e -> {
System.exit(-1);
});
GridBagConstraints gbc_btnCancel = new GridBagConstraints();
gbc_btnCancel.insets = new Insets(0, 25, 5, 25);
gbc_btnCancel.fill = GridBagConstraints.HORIZONTAL;
gbc_btnCancel.gridx = 0;
gbc_btnCancel.gridy = 2;
panel.add(btnCancel, gbc_btnCancel);
final JPanel buttonBar = new JPanel();
btnChangeSource = new JButton(i18n("button.change_source"));
btnCancel = new JButton(i18n("button.cancel"));
buttonBar.add(btnChangeSource);
buttonBar.add(btnCancel);
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(10, 5, 0, 5));
panel.add(content, BorderLayout.CENTER);
panel.add(buttonBar, BorderLayout.SOUTH);
}
public void setStatus(String status) {
progressText.setText(status);
public void setCurrent(String component) {
progressText.setText(i18n("download.javafx.component", component));
}
public void setProgress(int current, int total) {
progressBar.setValue(current);
public void setProgressMaximum(int total) {
progressBar.setMaximum(total);
}
public void setProgress(int n) {
progressBar.setValue(n);
}
public void incrementProgress() {
progressBar.setValue(progressBar.getValue() + 1);
}
public void setOnCancel(Runnable action) {
btnCancel.addActionListener(e -> action.run());
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
action.run();
}
});
}
public void setOnChangeSource(Runnable action) {
btnChangeSource.addActionListener(e -> action.run());
}
}
}

View File

@ -140,6 +140,7 @@ assets.download_all=Asset Integrity Check
assets.index.malformed=Asset index malformed, you can retry by "Update Game Asset Files" in version settings.
button.cancel=Cancel
button.change_source=Change Download Source
button.clear=Clear
button.delete=Delete
button.edit=Edit
@ -283,7 +284,10 @@ download.provider.mojang=Mojang (OptiFine download service is provided by BMCLAP
download.provider.official=Try to load from official source
download.provider.balanced=Load from fastest source
download.provider.mirror=Try to load from mirror source
download.javafx=Downloading the JavaFX runtime components
download.javafx=Downloading necessary runtime components
download.javafx.notes=Downloading necessary components for HMCL through network.\nClick the "Change Download Source" button to see more details and select the download source, or click the "Cancel" button to stop and exit.\nNote: If the download speed is too slow, please try to change the download source.
download.javafx.component=Downloading the module %s
download.javafx.prepare=Ready to download
extension.bat=Windows Bat file
extension.mod=Mod file
@ -747,8 +751,8 @@ profile.use_relative_path=Use relative path for game directory if possible
repositories.custom=Custom Maven Repository (%s)
repositories.maven_central=Universal (Maven Central)
repositories.aliyun_mirror=Chinese mainland (Aliyun Maven Repository)
repositories.chooser=JavaFX is missing. Do you want to automatically download and load JavaFX runtime components from web?\nSelect 'Yes' to download the JavaFX runtime components from the specified download source and start the HMCL, or select 'No' to exit the program.\nDownload Source:
repositories.chooser.title=Do you want to download JavaFX?
repositories.chooser=JavaFX is missing, HMCL needs JavaFX to work.\nClick the 'Ok' to download the JavaFX runtime components from specified download source and start HMCL, or click the 'Cancel' to exit.\nSelect Download Sources:
repositories.chooser.title=Select a download source to download JavaFX
resourcepack=Resource Pack

View File

@ -140,6 +140,7 @@ assets.download_all=驗證資源檔案完整性
assets.index.malformed=資源檔案的索引檔案損壞,您可以在遊戲 [設定] 頁面右上角的設定按鈕中選擇 [更新遊戲資源檔案],以修復該問題
button.cancel=取消
button.change_source=切換下載源
button.clear=清除
button.delete=刪除
button.edit=編輯
@ -283,7 +284,10 @@ download.provider.mojang=官方伺服器 (OptiFine 自動安裝的下載來源
download.provider.official=儘量使用官方源(最新,但可能加載慢)
download.provider.balanced=選擇加載速度快的下載源(平衡,但可能不是最新)
download.provider.mirror=儘量使用鏡像源(加載快,但可能不是最新)
download.javafx=正在下載 JavaFX 運行時組件
download.javafx=正在下載必要的運行時組件
download.javafx.notes=正在通過網絡下載 HMCL 必要的運行時組件。\n點擊“切換下載源”按鈕查看詳情以及選擇下載源點擊“取消”按鈕停止並退出。\n注意如果下載速度過慢請嘗試切换下載源。
download.javafx.component=正在下載模塊 %s
download.javafx.prepare=準備開始下載
extension.bat=Windows 指令碼
extension.mod=模組檔案
@ -746,8 +750,8 @@ profile.use_relative_path=如可行,則在遊戲目錄使用相對路徑
repositories.custom=自定義 Maven 倉庫(%s
repositories.maven_central=全球Maven Central
repositories.aliyun_mirror=中國大陸(阿里雲 Maven 倉庫)
repositories.chooser=缺少 JavaFX 運行環境。是否需要從網路下載並加載 JavaFX 運行時組件?\n選擇“是”從指定下載源下載 JavaFX 運行時組件並啟動HMCL選擇“否”退出程式。\n下載源:
repositories.chooser.title=是否下載 JavaFX
repositories.chooser=缺少 JavaFX 運行環境HMCL 需要 JavaFX 才能正常運行。\n點擊“確認”從指定下載源下載 JavaFX 運行時組件並啟動 HMCL點擊“取消”退出程式。\n選擇下載源:
repositories.chooser.title=選擇下載源下載JavaFX
resourcepack=資源包

View File

@ -140,6 +140,7 @@ assets.download_all=检查资源文件完整性
assets.index.malformed=资源文件的索引文件损坏,您可以在游戏设置页面右上角的设置按钮中选择更新游戏资源文件以修复该问题
button.cancel=取消
button.change_source=切换下载源
button.clear=清除
button.delete=删除
button.edit=修改
@ -283,7 +284,10 @@ download.provider.mojang=官方OptiFine 自动安装使用 BMCLAPI 下载源
download.provider.official=尽量使用官方源(最新,但可能加载慢)
download.provider.balanced=选择加载速度快的下载源(平衡,但可能不是最新)
download.provider.mirror=尽量使用镜像源(加载快,但可能不是最新)
download.javafx=正在下载 JavaFX 运行时组件
download.javafx=正在下载必要的运行时组件
download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源点击“取消”按钮停止并退出。\n注意如果下载速度过慢请尝试切换下载源。
download.javafx.component=正在下载模块 %s
download.javafx.prepare=准备开始下载
extension.bat=Windows 脚本
extension.mod=模组文件
@ -746,8 +750,8 @@ profile.use_relative_path=若可能,游戏目录使用相对路径
repositories.custom=自定义 Maven 仓库(%s
repositories.maven_central=全球Maven Central
repositories.aliyun_mirror=中国大陆(阿里云 Maven 仓库)
repositories.chooser=缺少 JavaFX 运行环境,是否需要从网络下载并加载 JavaFX 运行时组件?\n选择“是”从指定下载源下载 JavaFX 运行时组件并启动 HMCL选择“否”退出程序。\n下载源:
repositories.chooser.title=是否下载 JavaFX
repositories.chooser=缺少 JavaFX 运行环境,HMCL 需要 JavaFX 才能正常运行。\n点击“确认”从指定下载源下载 JavaFX 运行时组件并启动 HMCL点击“取消”退出程序。\n选择下载源:
repositories.chooser.title=选择下载源下载 JavaFX
resourcepack=资源包