Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b30617a6b | ||
|
|
266c686a3a | ||
|
|
dc1df98277 | ||
|
|
def45cc3e4 | ||
|
|
8007f1958e | ||
|
|
b22e724a3f | ||
|
|
ed66b016db | ||
|
|
34890f21b3 | ||
|
|
9f85e4a63b | ||
|
|
7fcd4d94a6 | ||
|
|
84fbca7905 | ||
|
|
9add2387b9 | ||
|
|
a388450f94 | ||
|
|
fff7abc2ac | ||
|
|
e5587418c7 | ||
|
|
7c89fac1e9 | ||
|
|
bc9a9ffb72 | ||
|
|
46d27a04a2 | ||
|
|
0f33981c96 | ||
|
|
b396e0b208 | ||
|
|
1bf2f17538 | ||
|
|
608a531655 | ||
|
|
e57180eb15 | ||
|
|
3ae826d9ca |
No files matched your search
@@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 HMCL 3 用户 QQ 群:219177735 中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub Issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 HMCL 3 用户 QQ 群:219177735 中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
|
||||
如果你希望在 QQ 群中反馈问题,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
|
||||
@@ -24,5 +24,5 @@
|
||||
* 对游戏做的修改(是否自行通过安装器安装 Rift 等 API,以及 mod):
|
||||
* 游戏崩溃报告(如果有):
|
||||
* 启动器崩溃报告(如果有):
|
||||
* 启动器日志文件(在启动器设置中打开日志文件夹并将文件夹内所有文件打包发在这里):
|
||||
* 问题描述(如何触发问题):
|
||||
* 启动器日志文件(出现问题后点击启动器设置中的【导出启动器日志】按钮):
|
||||
* 问题描述(如何触发问题):
|
||||
+1
-1
@@ -112,7 +112,7 @@ def createExecutable(String suffix, String header) {
|
||||
|
||||
processResources {
|
||||
ext.convertToBSS = { String resource ->
|
||||
exclude resource
|
||||
// exclude resource
|
||||
doFirst {
|
||||
def cssFile = new File(this.projectDir, "src/main/resources/" + resource)
|
||||
def bssFile = new File(this.projectDir, "build/compiled-resources/" + resource[0..-4] + "bss")
|
||||
|
||||
@@ -20,9 +20,6 @@ package org.jackhuang.hmcl.game;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -122,14 +122,14 @@ public final class LauncherHelper {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.then(variables -> {
|
||||
.then(() -> {
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
else
|
||||
return dependencyManager.checkGameCompletionAsync(version);
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
|
||||
.then(var -> {
|
||||
.then(() -> {
|
||||
try {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if ("Curse".equals(configuration.getType()))
|
||||
@@ -141,44 +141,42 @@ public final class LauncherHelper {
|
||||
}
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(i18n("account.methods"), variables -> {
|
||||
.thenCompose(() -> Task.ofResult(i18n("account.methods"), () -> {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
return account.logIn();
|
||||
} catch (CredentialExpiredException e) {
|
||||
LOG.info("Credential has expired: " + e);
|
||||
variables.set("account", DialogController.logIn(account));
|
||||
return DialogController.logIn(account);
|
||||
} catch (AuthenticationException e) {
|
||||
LOG.warning("Authentication failed, try playing offline: " + e);
|
||||
variables.set("account",
|
||||
account.playOffline().orElseThrow(() -> e));
|
||||
return account.playOffline().orElseThrow(() -> e);
|
||||
}
|
||||
}))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING)))
|
||||
.then(Task.of(variables -> {
|
||||
variables.set("launcher", new HMCLGameLauncher(
|
||||
repository,
|
||||
selectedVersion,
|
||||
variables.get("account"),
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
launcherVisibility == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent())
|
||||
));
|
||||
}))
|
||||
.then(variables -> {
|
||||
DefaultLauncher launcher = variables.get("launcher");
|
||||
.thenApply(Schedulers.javafx(), authInfo -> {
|
||||
emitStatus(LoadingState.LAUNCHING);
|
||||
return authInfo;
|
||||
})
|
||||
.thenApply(authInfo -> new HMCLGameLauncher(
|
||||
repository,
|
||||
selectedVersion,
|
||||
authInfo,
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
launcherVisibility == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(authInfo, setting, gameVersion.isPresent())
|
||||
))
|
||||
.thenCompose(launcher -> { // launcher is prev task's result
|
||||
if (scriptFile == null) {
|
||||
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
|
||||
} else {
|
||||
return new LaunchTask<>(() -> {
|
||||
return new LaunchTask<ManagedProcess>(() -> {
|
||||
launcher.makeLaunchScript(scriptFile);
|
||||
return null;
|
||||
}).setName(i18n("version.launch_script"));
|
||||
}
|
||||
})
|
||||
.then(Task.of(variables -> {
|
||||
.thenAccept(process -> { // process is LaunchTask's result
|
||||
if (scriptFile == null) {
|
||||
ManagedProcess process = variables.get(LaunchTask.LAUNCH_ID);
|
||||
PROCESSES.add(process);
|
||||
if (launcherVisibility == LauncherVisibility.CLOSE)
|
||||
Launcher.stopApplication();
|
||||
@@ -187,13 +185,13 @@ public final class LauncherHelper {
|
||||
process.stop();
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
} else
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
|
||||
}))
|
||||
}
|
||||
})
|
||||
.executor();
|
||||
|
||||
launchingStepsPane.setExecutor(executor, false);
|
||||
@@ -236,7 +234,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
Controllers.dialog(message,
|
||||
scriptFile == null ? i18n("launch.failed") : i18n("version.launch_script.failed"),
|
||||
MessageBox.ERROR_MESSAGE);
|
||||
MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -263,7 +261,7 @@ public final class LauncherHelper {
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
|
||||
JavaVersion java = setting.getJavaVersion();
|
||||
if (java == null) {
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
@@ -282,10 +280,10 @@ public final class LauncherHelper {
|
||||
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
|
||||
// Minecraft 1.13 and later versions only support Java 8 or later.
|
||||
// Terminate launching operation.
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, null);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
@@ -301,10 +299,10 @@ public final class LauncherHelper {
|
||||
if (java8.isPresent()) {
|
||||
java8required = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageBox.INFORMATION_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageType.INFORMATION, onAccept);
|
||||
flag = true;
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, null);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -319,7 +317,7 @@ public final class LauncherHelper {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -351,7 +349,7 @@ public final class LauncherHelper {
|
||||
if (java64.isPresent()) {
|
||||
setting.setJavaVersion(java64.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -361,13 +359,13 @@ public final class LauncherHelper {
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
@@ -379,7 +377,7 @@ public final class LauncherHelper {
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2773") < 0);
|
||||
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
|
||||
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -429,13 +427,6 @@ public final class LauncherHelper {
|
||||
public void execute() throws Exception {
|
||||
setResult(supplier.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return LAUNCH_ID;
|
||||
}
|
||||
|
||||
static final String LAUNCH_ID = "launch";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -110,13 +110,13 @@ public final class ModpackHelper {
|
||||
|
||||
if (modpack.getManifest() instanceof CurseManifest)
|
||||
return new CurseInstallTask(profile.getDependency(), zipFile, modpack, ((CurseManifest) modpack.getManifest()), name)
|
||||
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure);
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure);
|
||||
else if (modpack.getManifest() instanceof HMCLModpackManifest)
|
||||
return new HMCLModpackInstallTask(profile, zipFile, modpack, name)
|
||||
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure);
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure);
|
||||
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
|
||||
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.then(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
@@ -33,6 +35,7 @@ import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
@@ -48,7 +51,8 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
@@ -69,8 +73,8 @@ public final class TexturesLoader {
|
||||
private final Map<String, String> metadata;
|
||||
|
||||
public LoadedTexture(BufferedImage image, Map<String, String> metadata) {
|
||||
this.image = image;
|
||||
this.metadata = metadata;
|
||||
this.image = requireNonNull(image);
|
||||
this.metadata = requireNonNull(metadata);
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
@@ -98,6 +102,10 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
public static LoadedTexture loadTexture(Texture texture) throws IOException {
|
||||
if (StringUtils.isBlank(texture.getUrl())) {
|
||||
throw new IOException("Texture url is empty");
|
||||
}
|
||||
|
||||
Path file = getTexturePath(texture);
|
||||
if (!Files.isRegularFile(file)) {
|
||||
// download it
|
||||
@@ -118,7 +126,11 @@ public final class TexturesLoader {
|
||||
try (InputStream in = Files.newInputStream(file)) {
|
||||
img = ImageIO.read(in);
|
||||
}
|
||||
return new LoadedTexture(img, texture.getMetadata());
|
||||
Map<String, String> metadata = texture.getMetadata();
|
||||
if (metadata == null) {
|
||||
metadata = emptyMap();
|
||||
}
|
||||
return new LoadedTexture(img, metadata);
|
||||
}
|
||||
// ====
|
||||
|
||||
@@ -142,7 +154,7 @@ public final class TexturesLoader {
|
||||
|
||||
public static ObjectBinding<LoadedTexture> skinBinding(YggdrasilService service, UUID uuid) {
|
||||
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(uuid));
|
||||
return MultiStepBinding.of(service.getProfileRepository().binding(uuid))
|
||||
return BindingMapping.of(service.getProfileRepository().binding(uuid))
|
||||
.map(profile -> profile
|
||||
.flatMap(it -> {
|
||||
try {
|
||||
@@ -152,20 +164,23 @@ public final class TexturesLoader {
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN))))
|
||||
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))
|
||||
.filter(it -> StringUtils.isNotBlank(it.getUrl())))
|
||||
.asyncMap(it -> {
|
||||
if (it.isPresent()) {
|
||||
Texture texture = it.get();
|
||||
try {
|
||||
return loadTexture(texture);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
|
||||
return getDefaultSkin(TextureModel.detectModelName(texture.getMetadata()));
|
||||
}
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return loadTexture(texture);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
|
||||
return uuidFallback;
|
||||
}
|
||||
}, POOL);
|
||||
} else {
|
||||
return uuidFallback;
|
||||
return CompletableFuture.completedFuture(uuidFallback);
|
||||
}
|
||||
}, uuidFallback, POOL);
|
||||
}, uuidFallback);
|
||||
}
|
||||
// ====
|
||||
|
||||
@@ -189,7 +204,7 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
public static ObjectBinding<Image> fxAvatarBinding(YggdrasilService service, UUID uuid, int size) {
|
||||
return MultiStepBinding.of(skinBinding(service, uuid))
|
||||
return BindingMapping.of(skinBinding(service, uuid))
|
||||
.map(it -> toAvatar(it.image, size))
|
||||
.map(img -> SwingFXUtils.toFXImage(img, null));
|
||||
}
|
||||
|
||||
@@ -33,8 +33,6 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
|
||||
import java.io.File;
|
||||
@@ -90,9 +88,9 @@ public final class Profile implements Observable {
|
||||
return global.get();
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty name;
|
||||
private final SimpleStringProperty name;
|
||||
|
||||
public ImmediateStringProperty nameProperty() {
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -131,8 +129,8 @@ public final class Profile implements Observable {
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {
|
||||
this.name = new ImmediateStringProperty(this, "name", name);
|
||||
gameDir = new ImmediateObjectProperty<>(this, "gameDir", initialGameDir);
|
||||
this.name = new SimpleStringProperty(this, "name", name);
|
||||
gameDir = new SimpleObjectProperty<>(this, "gameDir", initialGameDir);
|
||||
repository = new HMCLGameRepository(this, initialGameDir);
|
||||
this.global.set(global == null ? new VersionSetting() : global);
|
||||
this.selectedVersion.set(selectedVersion);
|
||||
|
||||
@@ -27,6 +27,7 @@ import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -132,7 +133,8 @@ public class Theme {
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> foregroundFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> config().getTheme().getForegroundColor(), config().themeProperty());
|
||||
return BindingMapping.of(config().themeProperty())
|
||||
.map(Theme::getForegroundColor);
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> blackFillBinding() {
|
||||
|
||||
@@ -21,13 +21,18 @@ import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateBooleanProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateIntegerProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
@@ -60,9 +65,9 @@ public final class VersionSetting {
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty usesGlobalProperty = new ImmediateBooleanProperty(this, "usesGlobal", false);
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", false);
|
||||
|
||||
public ImmediateBooleanProperty usesGlobalProperty() {
|
||||
public BooleanProperty usesGlobalProperty() {
|
||||
return usesGlobalProperty;
|
||||
}
|
||||
|
||||
@@ -84,9 +89,9 @@ public final class VersionSetting {
|
||||
|
||||
// java
|
||||
|
||||
private final ImmediateStringProperty javaProperty = new ImmediateStringProperty(this, "java", "");
|
||||
private final StringProperty javaProperty = new SimpleStringProperty(this, "java", "");
|
||||
|
||||
public ImmediateStringProperty javaProperty() {
|
||||
public StringProperty javaProperty() {
|
||||
return javaProperty;
|
||||
}
|
||||
|
||||
@@ -110,7 +115,7 @@ public final class VersionSetting {
|
||||
setDefaultJavaPath(null);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty defaultJavaPathProperty = new ImmediateStringProperty(this, "defaultJavaPath", "");
|
||||
private final StringProperty defaultJavaPathProperty = new SimpleStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
* Path to Java executable, or null if user customizes java directory.
|
||||
@@ -124,9 +129,9 @@ public final class VersionSetting {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty javaDirProperty = new ImmediateStringProperty(this, "javaDir", "");
|
||||
private final StringProperty javaDirProperty = new SimpleStringProperty(this, "javaDir", "");
|
||||
|
||||
public ImmediateStringProperty javaDirProperty() {
|
||||
public StringProperty javaDirProperty() {
|
||||
return javaDirProperty;
|
||||
}
|
||||
|
||||
@@ -141,9 +146,9 @@ public final class VersionSetting {
|
||||
javaDirProperty.set(javaDir);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty wrapperProperty = new ImmediateStringProperty(this, "wrapper", "");
|
||||
private final StringProperty wrapperProperty = new SimpleStringProperty(this, "wrapper", "");
|
||||
|
||||
public ImmediateStringProperty wrapperProperty() {
|
||||
public StringProperty wrapperProperty() {
|
||||
return wrapperProperty;
|
||||
}
|
||||
|
||||
@@ -158,9 +163,9 @@ public final class VersionSetting {
|
||||
wrapperProperty.set(wrapper);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty permSizeProperty = new ImmediateStringProperty(this, "permSize", "");
|
||||
private final StringProperty permSizeProperty = new SimpleStringProperty(this, "permSize", "");
|
||||
|
||||
public ImmediateStringProperty permSizeProperty() {
|
||||
public StringProperty permSizeProperty() {
|
||||
return permSizeProperty;
|
||||
}
|
||||
|
||||
@@ -175,9 +180,9 @@ public final class VersionSetting {
|
||||
permSizeProperty.set(permSize);
|
||||
}
|
||||
|
||||
private final ImmediateIntegerProperty maxMemoryProperty = new ImmediateIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
|
||||
private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
|
||||
|
||||
public ImmediateIntegerProperty maxMemoryProperty() {
|
||||
public IntegerProperty maxMemoryProperty() {
|
||||
return maxMemoryProperty;
|
||||
}
|
||||
|
||||
@@ -195,9 +200,9 @@ public final class VersionSetting {
|
||||
/**
|
||||
* The minimum memory that JVM can allocate for heap.
|
||||
*/
|
||||
private final ImmediateObjectProperty<Integer> minMemoryProperty = new ImmediateObjectProperty<>(this, "minMemory", null);
|
||||
private final ObjectProperty<Integer> minMemoryProperty = new SimpleObjectProperty<>(this, "minMemory", null);
|
||||
|
||||
public ImmediateObjectProperty<Integer> minMemoryProperty() {
|
||||
public ObjectProperty<Integer> minMemoryProperty() {
|
||||
return minMemoryProperty;
|
||||
}
|
||||
|
||||
@@ -209,9 +214,9 @@ public final class VersionSetting {
|
||||
minMemoryProperty.set(minMemory);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty preLaunchCommandProperty = new ImmediateStringProperty(this, "precalledCommand", "");
|
||||
private final StringProperty preLaunchCommandProperty = new SimpleStringProperty(this, "precalledCommand", "");
|
||||
|
||||
public ImmediateStringProperty preLaunchCommandProperty() {
|
||||
public StringProperty preLaunchCommandProperty() {
|
||||
return preLaunchCommandProperty;
|
||||
}
|
||||
|
||||
@@ -229,9 +234,9 @@ public final class VersionSetting {
|
||||
|
||||
// options
|
||||
|
||||
private final ImmediateStringProperty javaArgsProperty = new ImmediateStringProperty(this, "javaArgs", "");
|
||||
private final StringProperty javaArgsProperty = new SimpleStringProperty(this, "javaArgs", "");
|
||||
|
||||
public ImmediateStringProperty javaArgsProperty() {
|
||||
public StringProperty javaArgsProperty() {
|
||||
return javaArgsProperty;
|
||||
}
|
||||
|
||||
@@ -246,9 +251,9 @@ public final class VersionSetting {
|
||||
javaArgsProperty.set(javaArgs);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty minecraftArgsProperty = new ImmediateStringProperty(this, "minecraftArgs", "");
|
||||
private final StringProperty minecraftArgsProperty = new SimpleStringProperty(this, "minecraftArgs", "");
|
||||
|
||||
public ImmediateStringProperty minecraftArgsProperty() {
|
||||
public StringProperty minecraftArgsProperty() {
|
||||
return minecraftArgsProperty;
|
||||
}
|
||||
|
||||
@@ -263,9 +268,9 @@ public final class VersionSetting {
|
||||
minecraftArgsProperty.set(minecraftArgs);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty noJVMArgsProperty = new ImmediateBooleanProperty(this, "noJVMArgs", false);
|
||||
private final BooleanProperty noJVMArgsProperty = new SimpleBooleanProperty(this, "noJVMArgs", false);
|
||||
|
||||
public ImmediateBooleanProperty noJVMArgsProperty() {
|
||||
public BooleanProperty noJVMArgsProperty() {
|
||||
return noJVMArgsProperty;
|
||||
}
|
||||
|
||||
@@ -280,9 +285,9 @@ public final class VersionSetting {
|
||||
noJVMArgsProperty.set(noJVMArgs);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty notCheckJVMProperty = new ImmediateBooleanProperty(this, "notCheckJVM", false);
|
||||
private final BooleanProperty notCheckJVMProperty = new SimpleBooleanProperty(this, "notCheckJVM", false);
|
||||
|
||||
public ImmediateBooleanProperty notCheckJVMProperty() {
|
||||
public BooleanProperty notCheckJVMProperty() {
|
||||
return notCheckJVMProperty;
|
||||
}
|
||||
|
||||
@@ -297,9 +302,9 @@ public final class VersionSetting {
|
||||
notCheckJVMProperty.set(notCheckJVM);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty notCheckGameProperty = new ImmediateBooleanProperty(this, "notCheckGame", false);
|
||||
private final BooleanProperty notCheckGameProperty = new SimpleBooleanProperty(this, "notCheckGame", false);
|
||||
|
||||
public ImmediateBooleanProperty notCheckGameProperty() {
|
||||
public BooleanProperty notCheckGameProperty() {
|
||||
return notCheckGameProperty;
|
||||
}
|
||||
|
||||
@@ -314,9 +319,9 @@ public final class VersionSetting {
|
||||
notCheckGameProperty.set(notCheckGame);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty showLogsProperty = new ImmediateBooleanProperty(this, "showLogs", false);
|
||||
private final BooleanProperty showLogsProperty = new SimpleBooleanProperty(this, "showLogs", false);
|
||||
|
||||
public ImmediateBooleanProperty showLogsProperty() {
|
||||
public BooleanProperty showLogsProperty() {
|
||||
return showLogsProperty;
|
||||
}
|
||||
|
||||
@@ -333,14 +338,14 @@ public final class VersionSetting {
|
||||
|
||||
// Minecraft settings.
|
||||
|
||||
private final ImmediateStringProperty serverIpProperty = new ImmediateStringProperty(this, "serverIp", "");
|
||||
private final StringProperty serverIpProperty = new SimpleStringProperty(this, "serverIp", "");
|
||||
|
||||
public ImmediateStringProperty serverIpProperty() {
|
||||
public StringProperty serverIpProperty() {
|
||||
return serverIpProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The server ip that will be entered after Minecraft successfully loaded immediately.
|
||||
* The server ip that will be entered after Minecraft successfully loaded ly.
|
||||
*
|
||||
* Format: ip:port or without port.
|
||||
*/
|
||||
@@ -353,9 +358,9 @@ public final class VersionSetting {
|
||||
}
|
||||
|
||||
|
||||
private final ImmediateBooleanProperty fullscreenProperty = new ImmediateBooleanProperty(this, "fullscreen", false);
|
||||
private final BooleanProperty fullscreenProperty = new SimpleBooleanProperty(this, "fullscreen", false);
|
||||
|
||||
public ImmediateBooleanProperty fullscreenProperty() {
|
||||
public BooleanProperty fullscreenProperty() {
|
||||
return fullscreenProperty;
|
||||
}
|
||||
|
||||
@@ -370,9 +375,9 @@ public final class VersionSetting {
|
||||
fullscreenProperty.set(fullscreen);
|
||||
}
|
||||
|
||||
private final ImmediateIntegerProperty widthProperty = new ImmediateIntegerProperty(this, "width", 854);
|
||||
private final IntegerProperty widthProperty = new SimpleIntegerProperty(this, "width", 854);
|
||||
|
||||
public ImmediateIntegerProperty widthProperty() {
|
||||
public IntegerProperty widthProperty() {
|
||||
return widthProperty;
|
||||
}
|
||||
|
||||
@@ -392,9 +397,9 @@ public final class VersionSetting {
|
||||
}
|
||||
|
||||
|
||||
private final ImmediateIntegerProperty heightProperty = new ImmediateIntegerProperty(this, "height", 480);
|
||||
private final IntegerProperty heightProperty = new SimpleIntegerProperty(this, "height", 480);
|
||||
|
||||
public ImmediateIntegerProperty heightProperty() {
|
||||
public IntegerProperty heightProperty() {
|
||||
return heightProperty;
|
||||
}
|
||||
|
||||
@@ -417,9 +422,9 @@ public final class VersionSetting {
|
||||
* 0 - .minecraft<br/>
|
||||
* 1 - .minecraft/versions/<version>/<br/>
|
||||
*/
|
||||
private final ImmediateObjectProperty<EnumGameDirectory> gameDirTypeProperty = new ImmediateObjectProperty<>(this, "gameDirType", EnumGameDirectory.ROOT_FOLDER);
|
||||
private final ObjectProperty<EnumGameDirectory> gameDirTypeProperty = new SimpleObjectProperty<>(this, "gameDirType", EnumGameDirectory.ROOT_FOLDER);
|
||||
|
||||
public ImmediateObjectProperty<EnumGameDirectory> gameDirTypeProperty() {
|
||||
public ObjectProperty<EnumGameDirectory> gameDirTypeProperty() {
|
||||
return gameDirTypeProperty;
|
||||
}
|
||||
|
||||
@@ -434,9 +439,9 @@ public final class VersionSetting {
|
||||
/**
|
||||
* Your custom gameDir
|
||||
*/
|
||||
private final ImmediateStringProperty gameDirProperty = new ImmediateStringProperty(this, "gameDir", "");
|
||||
private final StringProperty gameDirProperty = new SimpleStringProperty(this, "gameDir", "");
|
||||
|
||||
public ImmediateStringProperty gameDirProperty() {
|
||||
public StringProperty gameDirProperty() {
|
||||
return gameDirProperty;
|
||||
}
|
||||
|
||||
@@ -455,9 +460,9 @@ public final class VersionSetting {
|
||||
* 1 - Hide the launcher when the game starts.<br/>
|
||||
* 2 - Keep the launcher open.<br/>
|
||||
*/
|
||||
private final ImmediateObjectProperty<LauncherVisibility> launcherVisibilityProperty = new ImmediateObjectProperty<>(this, "launcherVisibility", LauncherVisibility.HIDE);
|
||||
private final ObjectProperty<LauncherVisibility> launcherVisibilityProperty = new SimpleObjectProperty<>(this, "launcherVisibility", LauncherVisibility.HIDE);
|
||||
|
||||
public ImmediateObjectProperty<LauncherVisibility> launcherVisibilityProperty() {
|
||||
public ObjectProperty<LauncherVisibility> launcherVisibilityProperty() {
|
||||
return launcherVisibilityProperty;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountList;
|
||||
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||
@@ -45,7 +46,7 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
@@ -158,7 +159,7 @@ public final class Controllers {
|
||||
});
|
||||
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
|
||||
mainPage.latestVersionProperty().bind(
|
||||
MultiStepBinding.of(UpdateChecker.latestVersionProperty())
|
||||
BindingMapping.of(UpdateChecker.latestVersionProperty())
|
||||
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
|
||||
|
||||
Profiles.registerVersionsListener(profile -> {
|
||||
@@ -223,14 +224,14 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title) {
|
||||
dialog(text, title, MessageBox.INFORMATION_MESSAGE);
|
||||
dialog(text, title, MessageType.INFORMATION);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type) {
|
||||
public static void dialog(String text, String title, MessageType type) {
|
||||
dialog(text, title, type, null);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type, Runnable onAccept) {
|
||||
public static void dialog(String text, String title, MessageType type, Runnable onAccept) {
|
||||
dialog(new MessageDialogPane(text, title, type, onAccept));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,17 +20,18 @@ package org.jackhuang.hmcl.ui;
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.task.SilentException;
|
||||
import org.jackhuang.hmcl.ui.account.AccountLoginPane;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class DialogController {
|
||||
|
||||
public static AuthInfo logIn(Account account) throws Exception {
|
||||
public static AuthInfo logIn(Account account) throws CancellationException, AuthenticationException, InterruptedException {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
@@ -42,7 +43,7 @@ public final class DialogController {
|
||||
Controllers.dialog(pane);
|
||||
});
|
||||
latch.await();
|
||||
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
|
||||
return Optional.ofNullable(res.get()).orElseThrow(CancellationException::new);
|
||||
}
|
||||
return account.logIn();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
@@ -331,7 +332,7 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<Number> property) {
|
||||
textField.textProperty().bindBidirectional(property, SafeIntStringConverter.INSTANCE);
|
||||
textField.textProperty().bindBidirectional(property, SafeStringConverter.fromInteger());
|
||||
}
|
||||
|
||||
public static void unbindInt(JFXTextField textField, Property<Number> property) {
|
||||
@@ -510,6 +511,7 @@ public final class FXUtils {
|
||||
public void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (!empty) {
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(graphicBuilder.apply(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -120,14 +119,13 @@ public final class LeftPaneController extends AdvancedListBox {
|
||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||
if (modpackFile.exists()) {
|
||||
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
||||
.thenResult(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
.thenResult(modpack -> {
|
||||
.thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
.thenAccept(modpack -> {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), this::checkAccount)).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.installing")));
|
||||
executor.start();
|
||||
return null;
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,11 +201,11 @@ public final class LogWindow extends Stage {
|
||||
if (!flag)
|
||||
cboLines.getSelectionModel().select(0);
|
||||
|
||||
btnFatals.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(fatal.get()) + " fatals", fatal));
|
||||
btnErrors.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(error.get()) + " errors", error));
|
||||
btnWarns.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(warn.get()) + " warns", warn));
|
||||
btnInfos.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(info.get()) + " infos", info));
|
||||
btnDebugs.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(debug.get()) + " debugs", debug));
|
||||
btnFatals.textProperty().bind(Bindings.concat(fatal, " fatals"));
|
||||
btnErrors.textProperty().bind(Bindings.concat(error, " errors"));
|
||||
btnWarns.textProperty().bind(Bindings.concat(warn, " warns"));
|
||||
btnInfos.textProperty().bind(Bindings.concat(info, " infos"));
|
||||
btnDebugs.textProperty().bind(Bindings.concat(debug, " debugs"));
|
||||
|
||||
btnFatals.selectedProperty().addListener(o -> specificChanged());
|
||||
btnErrors.selectedProperty().addListener(o -> specificChanged());
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.util.StringConverter;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class SafeIntStringConverter extends StringConverter<Number> {
|
||||
public static final SafeIntStringConverter INSTANCE = new SafeIntStringConverter();
|
||||
|
||||
private SafeIntStringConverter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
return Optional.ofNullable(string).map(Lang::toIntOrNull).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Number object) {
|
||||
return Optional.ofNullable(object).map(Object::toString).orElse("");
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
@@ -228,7 +228,7 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
try {
|
||||
Files.write(logFile, Logging.getRawLogs());
|
||||
} catch (IOException e) {
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageBox.ERROR_MESSAGE));
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageType.ERROR));
|
||||
LOG.log(Level.WARNING, "Failed to export logs", e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AccountListItem extends RadioButton {
|
||||
@@ -84,6 +85,8 @@ public class AccountListItem extends RadioButton {
|
||||
} catch (CredentialExpiredException e) {
|
||||
try {
|
||||
DialogController.logIn(account);
|
||||
} catch (CancellationException e1) {
|
||||
// ignore cancellation
|
||||
} catch (Exception e1) {
|
||||
LOG.log(Level.WARNING, "Failed to refresh " + account + " with password", e1);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -75,7 +76,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
if (skinnable.getAccount() instanceof AuthlibInjectorAccount) {
|
||||
Tooltip tooltip = new Tooltip();
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) skinnable.getAccount()).getServer();
|
||||
tooltip.textProperty().bind(Bindings.createStringBinding(server::toString, server));
|
||||
tooltip.textProperty().bind(BindingMapping.of(server, AuthlibInjectorServer::toString));
|
||||
FXUtils.installSlowTooltip(subtitle, tooltip);
|
||||
}
|
||||
VBox item = new VBox(title, subtitle);
|
||||
|
||||
@@ -62,7 +62,7 @@ public class AccountLoginPane extends StackPane {
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult(() -> oldAccount.logInWithPassword(password))
|
||||
.finalizedResult(Schedulers.javafx(), authInfo -> {
|
||||
.whenComplete(Schedulers.javafx(), authInfo -> {
|
||||
success.accept(authInfo);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
progressBar.setVisible(false);
|
||||
|
||||
@@ -47,7 +47,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -126,7 +126,7 @@ public class AddAccountPane extends StackPane {
|
||||
cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty()));
|
||||
|
||||
// authlib-injector links
|
||||
links.bind(MultiStepBinding.of(cboServers.getSelectionModel().selectedItemProperty())
|
||||
links.bind(BindingMapping.of(cboServers.getSelectionModel().selectedItemProperty())
|
||||
.map(AddAccountPane::createHyperlinks)
|
||||
.map(FXCollections::observableList));
|
||||
Bindings.bindContent(linksContainer.getChildren(), links);
|
||||
@@ -196,7 +196,7 @@ public class AddAccountPane extends StackPane {
|
||||
Object additionalData = getAuthAdditionalData();
|
||||
|
||||
Task.ofResult(() -> factory.create(new Selector(), username, password, additionalData))
|
||||
.finalizedResult(Schedulers.javafx(), account -> {
|
||||
.whenComplete(Schedulers.javafx(), account -> {
|
||||
int oldIndex = Accounts.getAccounts().indexOf(account);
|
||||
if (oldIndex == -1) {
|
||||
Accounts.getAccounts().add(account);
|
||||
|
||||
@@ -106,11 +106,11 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.locateServer(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
}).whenComplete(Schedulers.javafx(), (isDependentSucceeded, exception) -> {
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
if (isDependentSucceeded) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
@@ -118,7 +118,6 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else {
|
||||
Exception exception = variables.get("lastException");
|
||||
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import static javafx.collections.FXCollections.emptyObservableList;
|
||||
import static javafx.collections.FXCollections.observableList;
|
||||
import static javafx.collections.FXCollections.singletonObservableList;
|
||||
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXListCell;
|
||||
@@ -49,7 +49,7 @@ public class FontComboBox extends JFXComboBox<String> {
|
||||
}
|
||||
});
|
||||
|
||||
itemsProperty().bind(MultiStepBinding.of(valueProperty())
|
||||
itemsProperty().bind(BindingMapping.of(valueProperty())
|
||||
.map(value -> value == null ? emptyObservableList() : singletonObservableList(value)));
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MessageBox {
|
||||
private MessageBox() {
|
||||
}
|
||||
|
||||
private static final String TITLE = i18n("message.info");
|
||||
|
||||
/**
|
||||
* User Operation: Yes
|
||||
*/
|
||||
public static final int YES_OPTION = 0;
|
||||
|
||||
/**
|
||||
* User Operation: No
|
||||
*/
|
||||
public static final int NO_OPTION = 1;
|
||||
|
||||
/**
|
||||
* User Operation: Cancel
|
||||
*/
|
||||
public static final int CANCEL_OPTION = 2;
|
||||
|
||||
/**
|
||||
* User Operation: OK
|
||||
*/
|
||||
public static final int OK_OPTION = 0;
|
||||
|
||||
/**
|
||||
* User Operation: Closed Message Box
|
||||
*/
|
||||
public static final int CLOSED_OPTION = -1;
|
||||
|
||||
/**
|
||||
* Buttons: Yes No
|
||||
*/
|
||||
public static final int YES_NO_OPTION = 10;
|
||||
/**
|
||||
* Buttons: Yes No Cancel
|
||||
*/
|
||||
public static final int YES_NO_CANCEL_OPTION = 11;
|
||||
/**
|
||||
* Buttons: OK Cancel
|
||||
*/
|
||||
public static final int OK_CANCEL_OPTION = 12;
|
||||
|
||||
/**
|
||||
* Message Box Type: Error
|
||||
*/
|
||||
public static final int ERROR_MESSAGE = 0;
|
||||
/**
|
||||
* Message Box Type: Info
|
||||
*/
|
||||
public static final int INFORMATION_MESSAGE = 1;
|
||||
/**
|
||||
* Message Box Type: Warning
|
||||
*/
|
||||
public static final int WARNING_MESSAGE = 2;
|
||||
/**
|
||||
* Message Box Type: Question
|
||||
*/
|
||||
public static final int QUESTION_MESSAGE = 3;
|
||||
/**
|
||||
* Message Box Type: Fine
|
||||
*/
|
||||
public static final int FINE_MESSAGE = 4;
|
||||
/**
|
||||
* Message Box Type: Plain
|
||||
*/
|
||||
public static final int PLAIN_MESSAGE = -1;
|
||||
|
||||
|
||||
public static void show(String message) {
|
||||
show(message, TITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show MsgBox with title and options
|
||||
*
|
||||
* @param message The Message
|
||||
* @param title The title of MsgBox.
|
||||
* @return user operation.
|
||||
*/
|
||||
public static Optional<ButtonType> show(String message, String title) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
return alert.showAndWait();
|
||||
}
|
||||
|
||||
public static int confirm(String message, String title) {
|
||||
return confirm(message, title, -1);
|
||||
}
|
||||
|
||||
public static int confirm(String message, int option) {
|
||||
return confirm(message, TITLE, option);
|
||||
}
|
||||
|
||||
public static int confirm(String message, String title, int option) {
|
||||
return FXUtils.runInUIThread(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
switch (option) {
|
||||
case YES_NO_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
||||
break;
|
||||
case YES_NO_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
|
||||
break;
|
||||
case OK_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box option " + option);
|
||||
}
|
||||
Optional<ButtonType> buttonType = alert.showAndWait();
|
||||
if (!buttonType.isPresent()) return CLOSED_OPTION;
|
||||
else if (buttonType.get() == ButtonType.OK) return OK_OPTION;
|
||||
else if (buttonType.get() == ButtonType.YES) return YES_OPTION;
|
||||
else if (buttonType.get() == ButtonType.NO) return NO_OPTION;
|
||||
else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION;
|
||||
else throw new IllegalStateException("Unexpected button type:" + buttonType.get());
|
||||
});
|
||||
}
|
||||
|
||||
public static Optional<String> input(String message) {
|
||||
return input(message, UIManager.getString("OptionPane.inputDialogTitle"));
|
||||
}
|
||||
|
||||
public static Optional<String> input(String message, String title) {
|
||||
return input(message, title, "");
|
||||
}
|
||||
|
||||
public static Optional<String> input(String message, String title, String initialValue) {
|
||||
TextInputDialog dialog = new TextInputDialog(initialValue);
|
||||
dialog.setTitle(title);
|
||||
dialog.setHeaderText(message);
|
||||
dialog.setContentText(message);
|
||||
|
||||
return dialog.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,15 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
|
||||
public static enum MessageType {
|
||||
ERROR,
|
||||
INFORMATION,
|
||||
WARNING,
|
||||
QUESTION,
|
||||
FINE,
|
||||
PLAIN,
|
||||
}
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@FXML
|
||||
@@ -45,7 +54,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
@FXML
|
||||
private HBox actions;
|
||||
|
||||
public MessageDialogPane(String text, String title, int type, Runnable onAccept) {
|
||||
public MessageDialogPane(String text, String title, MessageType type, Runnable onAccept) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
|
||||
if (title != null)
|
||||
@@ -60,19 +69,19 @@ public final class MessageDialogPane extends StackPane {
|
||||
actions.getChildren().remove(cancelButton);
|
||||
|
||||
switch (type) {
|
||||
case MessageBox.INFORMATION_MESSAGE:
|
||||
case INFORMATION:
|
||||
graphic.setGraphic(SVG.infoCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.ERROR_MESSAGE:
|
||||
case ERROR:
|
||||
graphic.setGraphic(SVG.closeCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.FINE_MESSAGE:
|
||||
case FINE:
|
||||
graphic.setGraphic(SVG.checkCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.WARNING_MESSAGE:
|
||||
case WARNING:
|
||||
graphic.setGraphic(SVG.alert(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
case MessageBox.QUESTION_MESSAGE:
|
||||
case QUESTION:
|
||||
graphic.setGraphic(SVG.helpCircle(Theme.blackFillBinding(), 40, 40));
|
||||
break;
|
||||
default:
|
||||
@@ -81,7 +90,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
|
||||
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
this(text, title, MessageType.QUESTION, onAccept);
|
||||
|
||||
cancelButton.setVisible(true);
|
||||
cancelButton.setOnMouseClicked(e -> {
|
||||
|
||||
@@ -22,13 +22,15 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -89,16 +91,16 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next));
|
||||
|
||||
Task ret = Task.ofResult("version", () -> version);
|
||||
TaskResult<Version> ret = Task.ofResult(() -> version);
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
|
||||
|
||||
return ret.then(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
@@ -121,11 +123,13 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
|
||||
public static void alertFailureMessage(Exception exception, Runnable next) {
|
||||
if (exception instanceof LibraryDownloadException) {
|
||||
Controllers.dialog(i18n("launch.failed.download_library", ((LibraryDownloadException) exception).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next);
|
||||
Controllers.dialog(i18n("launch.failed.download_library", ((LibraryDownloadException) exception).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof DownloadException) {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", ((DownloadException) exception).getUrl()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next);
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", ((DownloadException) exception).getUrl()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException) {
|
||||
Controllers.dialog(i18n("install.failed.optifine_conflict"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageBox.ERROR_MESSAGE, next);
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageType.ERROR, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
@@ -84,11 +84,11 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
try {
|
||||
return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));
|
||||
} catch (UnsupportedModpackException e) {
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageType.ERROR);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageType.ERROR);
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageType.ERROR);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
@@ -105,9 +105,9 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
public void onFail(Map<String, Object> settings, Exception exception, Runnable next) {
|
||||
if (exception instanceof CurseCompletionException) {
|
||||
if (exception.getCause() instanceof FileNotFoundException) {
|
||||
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageBox.ERROR_MESSAGE, next);
|
||||
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(i18n("modpack.type.curse.tolerable_error"), i18n("install.success"), MessageBox.INFORMATION_MESSAGE, next);
|
||||
Controllers.dialog(i18n("modpack.type.curse.tolerable_error"), i18n("install.success"), MessageType.INFORMATION, next);
|
||||
}
|
||||
} else {
|
||||
InstallerWizardProvider.alertFailureMessage(exception, next);
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WebStage;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -110,8 +110,8 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
|
||||
spinnerPane.showSpinner();
|
||||
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
|
||||
.thenResult(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
|
||||
.finalizedResult(Schedulers.javafx(), manifest -> {
|
||||
.thenApply(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
|
||||
.whenComplete(Schedulers.javafx(), manifest -> {
|
||||
spinnerPane.hideSpinner();
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
@@ -129,7 +129,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
|
||||
}
|
||||
}, e -> {
|
||||
Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageType.ERROR);
|
||||
Platform.runLater(controller::onEnd);
|
||||
}).start();
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version((RemoteVersion) settings.get("optifine"));
|
||||
|
||||
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions())
|
||||
return builder.buildAsync().whenComplete((a, b) -> profile.getRepository().refreshVersions())
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
|
||||
@@ -128,8 +128,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@Override
|
||||
public void refresh() {
|
||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
executor = versionList.refreshAsync(gameVersion, downloadProvider).finalized((variables, isDependentsSucceeded) -> {
|
||||
if (isDependentsSucceeded) {
|
||||
executor = versionList.refreshAsync(gameVersion, downloadProvider).whenComplete((isDependentSucceeded, exception) -> {
|
||||
if (isDependentSucceeded) {
|
||||
List<VersionsPageItem> items = loadVersions();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -147,7 +147,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG.log(Level.WARNING, "Failed to fetch versions list", (Throwable) variables.get("lastException"));
|
||||
LOG.log(Level.WARNING, "Failed to fetch versions list", exception);
|
||||
Platform.runLater(() -> {
|
||||
transitionHandler.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 12;");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
@@ -25,11 +26,20 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.handleUncaught;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GameItem extends Control {
|
||||
|
||||
private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool("VersionResolve", true, 1, 1, TimeUnit.SECONDS);
|
||||
|
||||
private final Profile profile;
|
||||
private final String version;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
@@ -40,16 +50,19 @@ public class GameItem extends Control {
|
||||
this.profile = profile;
|
||||
this.version = id;
|
||||
|
||||
String game = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown");
|
||||
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
|
||||
analyzer.getForge().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
|
||||
analyzer.getLiteLoader().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
|
||||
analyzer.getOptiFine().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
|
||||
// GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)
|
||||
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown"), POOL_VERSION_RESOLVE)
|
||||
.thenAcceptAsync(game -> {
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
|
||||
analyzer.getForge().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
|
||||
analyzer.getLiteLoader().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
|
||||
analyzer.getOptiFine().ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
|
||||
subtitle.set(libraries.toString());
|
||||
}, Platform::runLater)
|
||||
.exceptionally(handleUncaught);
|
||||
|
||||
title.set(id);
|
||||
subtitle.set(libraries.toString());
|
||||
image.set(profile.getRepository().getVersionIconImage(version));
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class InstallerListPage extends ListPage<InstallerItem> {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new MaintainTask(version.setLibraries(newList))
|
||||
.then(variables -> new VersionJsonSaveTask(profile.getRepository(), variables.get(MaintainTask.ID)))
|
||||
.then(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
|
||||
.with(profile.getRepository().refreshVersionsAsync())
|
||||
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
|
||||
.start();
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
@@ -86,15 +85,15 @@ public final class ModListPage extends Control {
|
||||
|
||||
public void loadMods(ModManager modManager) {
|
||||
this.modManager = modManager;
|
||||
Task.ofResult("list", variables -> {
|
||||
Task.ofResult(() -> {
|
||||
synchronized (ModListPage.this) {
|
||||
JFXUtilities.runInFX(() -> loadingProperty().set(true));
|
||||
modManager.refreshMods();
|
||||
return new LinkedList<>(modManager.getMods());
|
||||
}
|
||||
}).finalizedResult(Schedulers.javafx(), (list, isDependentsSucceeded) -> {
|
||||
}).whenComplete(Schedulers.javafx(), (list, isDependentSucceeded, exception) -> {
|
||||
loadingProperty().set(false);
|
||||
if (isDependentsSucceeded)
|
||||
if (isDependentSucceeded)
|
||||
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
if (newValue != null && newValue.getUserData() == ModListPage.this)
|
||||
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
||||
@@ -112,7 +111,7 @@ public final class ModListPage extends Control {
|
||||
List<String> succeeded = new LinkedList<>();
|
||||
List<String> failed = new LinkedList<>();
|
||||
if (res == null) return;
|
||||
Task.of(variables -> {
|
||||
Task.of(() -> {
|
||||
for (File file : res) {
|
||||
try {
|
||||
modManager.addMod(file);
|
||||
@@ -124,7 +123,7 @@ public final class ModListPage extends Control {
|
||||
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
|
||||
}
|
||||
}
|
||||
}).with(Task.of(Schedulers.javafx(), variables -> {
|
||||
}).with(Task.of(Schedulers.javafx(), () -> {
|
||||
List<String> prompt = new LinkedList<>();
|
||||
if (!succeeded.isEmpty())
|
||||
prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
|
||||
@@ -57,7 +57,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -71,7 +71,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private boolean javaItemsLoaded;
|
||||
private InvalidationListener specificSettingsListener;
|
||||
|
||||
@FXML private VBox rootPane;
|
||||
@FXML private ScrollPane scroll;
|
||||
@@ -100,6 +99,12 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
@FXML private JFXCheckBox chkEnableSpecificSettings;
|
||||
@FXML private BorderPane settingsTypePane;
|
||||
|
||||
private InvalidationListener specificSettingsListener = any -> {
|
||||
chkEnableSpecificSettings.setSelected(!lastVersionSetting.isUsesGlobal());
|
||||
};
|
||||
|
||||
private InvalidationListener javaListener = any -> initJavaSubtitle();
|
||||
|
||||
public VersionSettingsPage() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/version-settings.fxml");
|
||||
|
||||
@@ -113,14 +118,13 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
Task.ofResult(JavaVersion::getJavas).thenResult(Schedulers.javafx(), list -> {
|
||||
Task.ofResult(JavaVersion::getJavas).thenAccept(Schedulers.javafx(), list -> {
|
||||
javaItem.loadChildren(list.stream()
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit",
|
||||
javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion))
|
||||
.collect(Collectors.toList()));
|
||||
javaItemsLoaded = true;
|
||||
initializeSelectedJava();
|
||||
return null;
|
||||
}).start();
|
||||
|
||||
javaItem.setSelectedData(null);
|
||||
@@ -148,10 +152,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
Platform.runLater(() -> loadVersion(profile, versionId));
|
||||
});
|
||||
|
||||
specificSettingsListener = o -> {
|
||||
chkEnableSpecificSettings.setSelected(!lastVersionSetting.isUsesGlobal());
|
||||
};
|
||||
|
||||
componentList.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not());
|
||||
advancedSettingsPane.disableProperty().bind(chkEnableSpecificSettings.selectedProperty().not());
|
||||
}
|
||||
@@ -193,6 +193,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener);
|
||||
lastVersionSetting.javaDirProperty().removeListener(javaListener);
|
||||
lastVersionSetting.javaProperty().removeListener(javaListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().unbind();
|
||||
@@ -232,9 +234,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
});
|
||||
|
||||
versionSetting.javaDirProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
initJavaSubtitle(versionSetting);
|
||||
versionSetting.javaDirProperty().addListener(javaListener);
|
||||
versionSetting.javaProperty().addListener(javaListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(),
|
||||
@@ -243,6 +244,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
initializeSelectedJava();
|
||||
initJavaSubtitle();
|
||||
|
||||
loadIcon();
|
||||
}
|
||||
@@ -267,11 +269,14 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
}
|
||||
|
||||
private void initJavaSubtitle(VersionSetting versionSetting) {
|
||||
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
|
||||
.subscribe(Task.of(Schedulers.javafx(),
|
||||
variables -> javaItem.setSubtitle(variables.<JavaVersion>getOptional("java")
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Directory"))));
|
||||
private void initJavaSubtitle() {
|
||||
VersionSetting versionSetting = lastVersionSetting;
|
||||
if (versionSetting == null)
|
||||
return;
|
||||
Task.ofResult(versionSetting::getJavaVersion)
|
||||
.thenAccept(Schedulers.javafx(), javaVersion -> javaItem.setSubtitle(Optional.ofNullable(javaVersion)
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))
|
||||
.start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@@ -52,9 +51,9 @@ public class WorldListPage extends ListPage<WorldListItem> {
|
||||
|
||||
setLoading(true);
|
||||
Task.ofResult(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||
.finalizedResult(Schedulers.javafx(), (result, isDependentsSucceeded) -> {
|
||||
.whenComplete(Schedulers.javafx(), (result, isDependentSucceeded, exception) -> {
|
||||
setLoading(false);
|
||||
if (isDependentsSucceeded)
|
||||
if (isDependentSucceeded)
|
||||
itemsProperty().setAll(result.stream().map(WorldListItem::new).collect(Collectors.toList()));
|
||||
}).start();
|
||||
}
|
||||
@@ -74,10 +73,10 @@ public class WorldListPage extends ListPage<WorldListItem> {
|
||||
// Only accept one world file because user is required to confirm the new world name
|
||||
// Or too many input dialogs are popped.
|
||||
Task.ofResult(() -> new World(zipFile.toPath()))
|
||||
.finalizedResult(Schedulers.javafx(), world -> {
|
||||
.whenComplete(Schedulers.javafx(), world -> {
|
||||
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||
Task.of(() -> world.install(savesDir, name))
|
||||
.finalized(Schedulers.javafx(), var -> {
|
||||
.whenComplete(Schedulers.javafx(), () -> {
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
|
||||
resolve.run();
|
||||
}, e -> {
|
||||
|
||||
+5
-5
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
@@ -67,9 +67,9 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
pane.fireEvent(new DialogCloseEvent());
|
||||
if (success) {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageType.FINE, () -> onEnd());
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
Controllers.dialog(i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog(i18n("message.success"), null, MessageType.FINE, () -> onEnd());
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
return;
|
||||
@@ -77,9 +77,9 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
|
||||
((WizardProvider.FailureCallback)settings.get("failure_callback")).onFail(settings, executor.getLastException(), () -> onEnd());
|
||||
else if (settings.get("failure_message") instanceof String)
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageType.ERROR, () -> onEnd());
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
Controllers.dialog(appendix, i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> onEnd());
|
||||
Controllers.dialog(appendix, i18n("wizard.failed"), MessageType.ERROR, () -> onEnd());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
@@ -102,7 +102,7 @@ public final class UpdateHandler {
|
||||
Task task = new HMCLDownloadTask(version, downloaded);
|
||||
|
||||
TaskExecutor executor = task.executor();
|
||||
Region dialog = Controllers.taskDialog(executor, i18n("message.downloading"));
|
||||
Controllers.taskDialog(executor, i18n("message.downloading"));
|
||||
thread(() -> {
|
||||
boolean success = executor.test();
|
||||
|
||||
@@ -116,14 +116,14 @@ public final class UpdateHandler {
|
||||
System.exit(0);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageType.ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
Throwable e = task.getLastException();
|
||||
Throwable e = executor.getLastException();
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
@@ -69,7 +71,10 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
String info = entry.getValue();
|
||||
LOG.severe(info);
|
||||
try {
|
||||
MessageBox.show(info);
|
||||
Alert alert = new Alert(AlertType.INFORMATION, info);
|
||||
alert.setTitle(i18n("message.info"));
|
||||
alert.setHeaderText(i18n("message.info"));
|
||||
alert.showAndWait();
|
||||
} catch (Throwable t) {
|
||||
LOG.log(Level.SEVERE, "Unable to show message", t);
|
||||
}
|
||||
|
||||
@@ -58,10 +58,6 @@ public final class Locales {
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH);
|
||||
|
||||
public static SupportedLocale getLocale(int index) {
|
||||
return Lang.get(LOCALES, index).orElse(DEFAULT);
|
||||
}
|
||||
|
||||
public static SupportedLocale getLocaleByName(String name) {
|
||||
if (name == null) return DEFAULT;
|
||||
switch (name.toLowerCase()) {
|
||||
|
||||
@@ -95,7 +95,7 @@ download.failed=Failed to download
|
||||
download.failed.empty=No candidates. Click here to return.
|
||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=Mojang
|
||||
download.provider.mojang=Mojang (Forge and OptiFine installation being downloaded from BMCLAPI)
|
||||
|
||||
extension.bat=Windows Bat file
|
||||
extension.mod=Mod file
|
||||
@@ -127,6 +127,7 @@ install=Install New Game
|
||||
install.failed=Failed to install
|
||||
install.failed.downloading=Failed to install due to some files not downloaded successfully
|
||||
install.failed.downloading.detail=Failed to download file: %s
|
||||
install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13
|
||||
install.installer.choose=Choose a %s version
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Game
|
||||
|
||||
@@ -94,7 +94,7 @@ download.failed=下載失敗
|
||||
download.failed.empty=沒有可供安裝的版本,點擊此處返回。
|
||||
download.failed.refresh=載入版本列表失敗,點擊此處重試。
|
||||
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=官方伺服器(Mojang)
|
||||
download.provider.mojang=官方伺服器(Forge 和 OptiFine 自動安裝的下載來源是 BMCLAPI)
|
||||
|
||||
extension.bat=Windows 腳本
|
||||
extension.mod=模組檔案
|
||||
@@ -126,6 +126,7 @@ install=新增遊戲
|
||||
install.failed=安裝失敗
|
||||
install.failed.downloading=安裝失敗,部分文件未能完成下載
|
||||
install.failed.downloading.detail=未能下載檔案:%s
|
||||
install.failed.optifine_conflict=暫不支持 OptiFine 與 Forge 同時安裝於 Minecraft 1.13
|
||||
install.installer.choose=選擇 %s 版本
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=遊戲
|
||||
|
||||
@@ -94,7 +94,7 @@ download.failed=下载失败
|
||||
download.failed.empty=没有可供安装的版本,点击此处返回。
|
||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=官方(Mojang)
|
||||
download.provider.mojang=官方(Forge 和 OptiFine 自动安装使用 BMCLAPI 下载源)
|
||||
|
||||
extension.bat=Windows 脚本
|
||||
extension.mod=模组文件
|
||||
@@ -126,6 +126,7 @@ install=添加游戏
|
||||
install.failed=安装失败
|
||||
install.failed.downloading=安装失败,部分文件未能完成下载
|
||||
install.failed.downloading.detail=未能下载文件:%s
|
||||
install.failed.optifine_conflict=暂不支持 OptiFine 与 Forge 同时安装于 Minecraft 1.13
|
||||
install.installer.choose=选择 %s 版本
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=游戏
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -36,10 +37,12 @@ public final class Texture {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
|
||||
/**
|
||||
@@ -71,7 +71,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
@Override
|
||||
public Task checkGameCompletionAsync(Version version) {
|
||||
return new ParallelTask(
|
||||
Task.ofThen(var -> {
|
||||
Task.ofThen(() -> {
|
||||
if (!repository.getVersionJar(version).exists())
|
||||
return new GameDownloadTask(this, null, version);
|
||||
else
|
||||
@@ -88,36 +88,32 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
|
||||
public TaskResult<Version> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
|
||||
VersionList<?> versionList = getVersionList(libraryId);
|
||||
return versionList.loadAsync(gameVersion, getDownloadProvider())
|
||||
.then(variables -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
|
||||
.thenCompose(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
|
||||
.orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task installLibraryAsync(Version version, RemoteVersion libraryVersion) {
|
||||
public TaskResult<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) {
|
||||
TaskResult<Version> task;
|
||||
if (libraryVersion instanceof ForgeRemoteVersion)
|
||||
return new ForgeInstallTask(this, version, (ForgeRemoteVersion) libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion);
|
||||
else if (libraryVersion instanceof LiteLoaderRemoteVersion)
|
||||
return new LiteLoaderInstallTask(this, version, (LiteLoaderRemoteVersion) libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
task = new LiteLoaderInstallTask(this, oldVersion, (LiteLoaderRemoteVersion) libraryVersion);
|
||||
else if (libraryVersion instanceof OptiFineRemoteVersion)
|
||||
return new OptiFineInstallTask(this, version, (OptiFineRemoteVersion) libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
task = new OptiFineInstallTask(this, oldVersion, (OptiFineRemoteVersion) libraryVersion);
|
||||
else
|
||||
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
|
||||
return task
|
||||
.thenCompose(LibrariesUniqueTask::new)
|
||||
.thenCompose(MaintainTask::new)
|
||||
.thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion));
|
||||
}
|
||||
|
||||
|
||||
public ExceptionalFunction<AutoTypingMap<String>, Task, ?> installLibraryAsync(RemoteVersion libraryVersion) {
|
||||
return var -> installLibraryAsync(var.get("version"), libraryVersion);
|
||||
public ExceptionalFunction<Version, TaskResult<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
|
||||
return version -> installLibraryAsync(version, libraryVersion);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import org.jackhuang.hmcl.download.game.*;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
@@ -49,34 +49,35 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
|
||||
@Override
|
||||
public Task buildAsync() {
|
||||
return new VersionJsonDownloadTask(gameVersion, dependencyManager).then(variables -> {
|
||||
Version version = JsonUtils.GSON.fromJson(variables.<String>get(VersionJsonDownloadTask.ID), Version.class);
|
||||
version = version.setId(name).setJar(null);
|
||||
variables.set("version", version);
|
||||
Task result = downloadGameAsync(gameVersion, version).then(new ParallelTask(
|
||||
return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenCompose(rawJson -> {
|
||||
Version original = JsonUtils.GSON.fromJson(rawJson, Version.class);
|
||||
Version version = original.setId(name).setJar(null);
|
||||
Task vanillaTask = downloadGameAsync(gameVersion, version).then(new ParallelTask(
|
||||
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
|
||||
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
|
||||
).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version))); // using [with] because download failure here are tolerant.
|
||||
|
||||
TaskResult<Version> libraryTask = vanillaTask.thenSupply(() -> version);
|
||||
|
||||
if (toolVersions.containsKey("forge"))
|
||||
result = result.then(libraryTaskHelper(gameVersion, "forge"));
|
||||
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "forge"));
|
||||
if (toolVersions.containsKey("liteloader"))
|
||||
result = result.then(libraryTaskHelper(gameVersion, "liteloader"));
|
||||
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "liteloader"));
|
||||
if (toolVersions.containsKey("optifine"))
|
||||
result = result.then(libraryTaskHelper(gameVersion, "optifine"));
|
||||
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "optifine"));
|
||||
|
||||
for (RemoteVersion remoteVersion : remoteVersions)
|
||||
result = result.then(dependencyManager.installLibraryAsync(remoteVersion));
|
||||
libraryTask = libraryTask.thenCompose(dependencyManager.installLibraryAsync(remoteVersion));
|
||||
|
||||
return result;
|
||||
}).finalized((variables, isDependentsSucceeded) -> {
|
||||
if (!isDependentsSucceeded)
|
||||
return libraryTask;
|
||||
}).whenComplete((isDependentSucceeded, exception) -> {
|
||||
if (!isDependentSucceeded)
|
||||
dependencyManager.getGameRepository().getVersionRoot(name).delete();
|
||||
});
|
||||
}
|
||||
|
||||
private ExceptionalFunction<AutoTypingMap<String>, Task, ?> libraryTaskHelper(String gameVersion, String libraryId) {
|
||||
return variables -> dependencyManager.installLibraryAsync(gameVersion, variables.get("version"), libraryId, toolVersions.get(libraryId));
|
||||
private ExceptionalFunction<Version, TaskResult<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId) {
|
||||
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, toolVersions.get(libraryId));
|
||||
}
|
||||
|
||||
protected Task downloadGameAsync(String gameVersion, Version version) {
|
||||
|
||||
@@ -23,15 +23,9 @@ import org.jackhuang.hmcl.task.TaskResult;
|
||||
public class MaintainTask extends TaskResult<Version> {
|
||||
|
||||
private final Version version;
|
||||
private final String id;
|
||||
|
||||
public MaintainTask(Version version) {
|
||||
this(version, ID);
|
||||
}
|
||||
|
||||
public MaintainTask(Version version, String id) {
|
||||
this.version = version;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,11 +70,4 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static final String ID = "version";
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
}
|
||||
|
||||
public Task loadAsync(DownloadProvider downloadProvider) {
|
||||
return Task.ofThen(variables -> {
|
||||
return Task.ofThen(() -> {
|
||||
lock.readLock().lock();
|
||||
boolean loaded;
|
||||
|
||||
@@ -88,7 +88,7 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
}
|
||||
|
||||
public Task loadAsync(String gameVersion, DownloadProvider downloadProvider) {
|
||||
return Task.ofThen(variables -> {
|
||||
return Task.ofThen(() -> {
|
||||
lock.readLock().lock();
|
||||
boolean loaded;
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
final GetTask task = new GetTask(NetworkUtils.toURL("https://bmclapi2.bangbang93.com/forge/minecraft/" + gameVersion));
|
||||
return new Task() {
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(task);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
private final String branch;
|
||||
private final String mcversion;
|
||||
private final String version;
|
||||
private final int build;
|
||||
private final List<File> files;
|
||||
|
||||
/**
|
||||
@@ -112,14 +111,13 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public ForgeVersion() {
|
||||
this(null, null, null, 0, null);
|
||||
this(null, null, null, null);
|
||||
}
|
||||
|
||||
public ForgeVersion(String branch, String mcversion, String version, int build, List<File> files) {
|
||||
public ForgeVersion(String branch, String mcversion, String version, List<File> files) {
|
||||
this.branch = branch;
|
||||
this.mcversion = mcversion;
|
||||
this.version = version;
|
||||
this.build = build;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@@ -135,10 +133,6 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
return version;
|
||||
}
|
||||
|
||||
public int getBuild() {
|
||||
return build;
|
||||
}
|
||||
|
||||
public List<File> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
public void preExecute() throws Exception {
|
||||
installer = Files.createTempFile("forge-installer", ".jar");
|
||||
|
||||
dependent = new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer.toFile());
|
||||
dependent = new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer.toFile())
|
||||
.setCaching(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,16 +83,6 @@ public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
return Collections.singleton(dependency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "version";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare("1.13", remote.getGameVersion()) <= 0)
|
||||
|
||||
+11
-1
@@ -34,14 +34,16 @@ public class ForgeNewInstallProfile {
|
||||
private final int spec;
|
||||
private final String minecraft;
|
||||
private final String json;
|
||||
private final Artifact path;
|
||||
private final List<Library> libraries;
|
||||
private final List<Processor> processors;
|
||||
private final Map<String, Datum> data;
|
||||
|
||||
public ForgeNewInstallProfile(int spec, String minecraft, String json, List<Library> libraries, List<Processor> processors, Map<String, Datum> data) {
|
||||
public ForgeNewInstallProfile(int spec, String minecraft, String json, Artifact path, List<Library> libraries, List<Processor> processors, Map<String, Datum> data) {
|
||||
this.spec = spec;
|
||||
this.minecraft = minecraft;
|
||||
this.json = json;
|
||||
this.path = path;
|
||||
this.libraries = libraries;
|
||||
this.processors = processors;
|
||||
this.data = data;
|
||||
@@ -69,6 +71,14 @@ public class ForgeNewInstallProfile {
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maven artifact path for the main jar to install.
|
||||
* @return artifact path of the main jar.
|
||||
*/
|
||||
public Artifact getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Libraries that processors depend on.
|
||||
* @return the required dependencies.
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
@@ -89,11 +90,6 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "version";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
@@ -112,6 +108,14 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
FileUtils.copyFile(file, dest);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Path mainJar = profile.getPath().getPath(fs.getPath("maven"));
|
||||
if (Files.exists(mainJar)) {
|
||||
Path dest = gameRepository.getArtifactFile(version, profile.getPath());
|
||||
FileUtils.copyFile(mainJar, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependents.add(new GameLibrariesTask(dependencyManager, version, profile.getLibraries()));
|
||||
@@ -119,6 +123,9 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass()))
|
||||
throw new OptiFineInstallTask.UnsupportedOptiFineInstallationException();
|
||||
|
||||
Path temp = Files.createTempDirectory("forge_installer");
|
||||
int finished = 0;
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
|
||||
@@ -49,11 +49,6 @@ public class ForgeOldInstallTask extends TaskResult<Version> {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "version";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
|
||||
@@ -89,6 +89,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation();
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task
|
||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
||||
.setCaching(true)
|
||||
|
||||
@@ -33,15 +33,9 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
private final Version version;
|
||||
private final String id;
|
||||
|
||||
public LibrariesUniqueTask(Version version) {
|
||||
this(version, "version");
|
||||
}
|
||||
|
||||
public LibrariesUniqueTask(Version version, String id) {
|
||||
this.version = version;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,9 +93,4 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
|
||||
setResult(version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -128,12 +128,12 @@ public class LibraryDownloadTask extends Task {
|
||||
try {
|
||||
URL packXz = NetworkUtils.toURL(url + ".pack.xz");
|
||||
if (NetworkUtils.urlExists(packXz)) {
|
||||
task = new FileDownloadTask(packXz, xzFile, null);
|
||||
task = new FileDownloadTask(packXz, xzFile, null).setCaching(true);
|
||||
xz = true;
|
||||
} else {
|
||||
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
||||
jar,
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null).setCaching(true);
|
||||
xz = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -225,16 +225,16 @@ public class LibraryDownloadTask extends Task {
|
||||
int x = decompressed.length;
|
||||
int len = decompressed[(x - 8)] & 0xFF | (decompressed[(x - 7)] & 0xFF) << 8 | (decompressed[(x - 6)] & 0xFF) << 16 | (decompressed[(x - 5)] & 0xFF) << 24;
|
||||
|
||||
File temp = FileUtils.createTempFile("minecraft", ".pack");
|
||||
Path temp = Files.createTempFile("minecraft", ".pack");
|
||||
|
||||
byte[] checksums = Arrays.copyOfRange(decompressed, decompressed.length - len - 8, decompressed.length - 8);
|
||||
|
||||
OutputStream out = new FileOutputStream(temp);
|
||||
out.write(decompressed, 0, decompressed.length - len - 8);
|
||||
out.close();
|
||||
try (OutputStream out = Files.newOutputStream(temp)) {
|
||||
out.write(decompressed, 0, decompressed.length - len - 8);
|
||||
}
|
||||
|
||||
try (FileOutputStream jarBytes = new FileOutputStream(dest); JarOutputStream jos = new JarOutputStream(jarBytes)) {
|
||||
Pack200.newUnpacker().unpack(temp, jos);
|
||||
Pack200.newUnpacker().unpack(temp.toFile(), jos);
|
||||
|
||||
JarEntry checksumsFile = new JarEntry("checksums.sha1");
|
||||
checksumsFile.setTime(0L);
|
||||
@@ -243,6 +243,6 @@ public class LibraryDownloadTask extends Task {
|
||||
jos.closeEntry();
|
||||
}
|
||||
|
||||
temp.delete();
|
||||
Files.delete(temp);
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -22,6 +22,7 @@ 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.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -32,7 +33,7 @@ import java.util.List;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class VersionJsonDownloadTask extends Task {
|
||||
public final class VersionJsonDownloadTask extends TaskResult<String> {
|
||||
private final String gameVersion;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
@@ -65,8 +66,6 @@ public final class VersionJsonDownloadTask extends Task {
|
||||
RemoteVersion remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot find specific version " + gameVersion + " in remote repository"));
|
||||
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl());
|
||||
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL), ID));
|
||||
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL)).storeTo(this::setResult));
|
||||
}
|
||||
|
||||
public static final String ID = "raw_version_json";
|
||||
}
|
||||
@@ -19,19 +19,18 @@ package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This task is to save the version json.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class VersionJsonSaveTask extends Task {
|
||||
public final class VersionJsonSaveTask extends TaskResult<Version> {
|
||||
|
||||
private final DefaultGameRepository repository;
|
||||
private final Version version;
|
||||
@@ -47,13 +46,12 @@ public final class VersionJsonSaveTask extends Task {
|
||||
this.version = version;
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
setResult(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
File json = repository.getVersionJson(version.getId()).getAbsoluteFile();
|
||||
if (!FileUtils.makeFile(json))
|
||||
throw new IOException("Cannot create file " + json);
|
||||
FileUtils.writeText(json, JsonUtils.GSON.toJson(version));
|
||||
}
|
||||
}
|
||||
-6
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.liteloader;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
|
||||
@@ -61,11 +60,6 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "version";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
Library library = new Library(
|
||||
|
||||
+5
-5
@@ -59,11 +59,6 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "version";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
@@ -71,6 +66,9 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if ("cpw.mods.modlauncher.Launcher".equals(version.getMainClass()))
|
||||
throw new UnsupportedOptiFineInstallationException();
|
||||
|
||||
String remoteVersion = remote.getGameVersion() + "_" + remote.getSelfVersion();
|
||||
|
||||
Library library = new Library(
|
||||
@@ -95,4 +93,6 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(version.setLibraries(libraries)));
|
||||
}
|
||||
|
||||
public static class UnsupportedOptiFineInstallationException extends UnsupportedOperationException {
|
||||
}
|
||||
}
|
||||
@@ -168,8 +168,8 @@ public class DefaultGameRepository implements GameRepository {
|
||||
|
||||
versions.remove(id);
|
||||
|
||||
if (FileUtils.isMovingToTrashSupported()) {
|
||||
return FileUtils.moveToTrash(removedFile);
|
||||
if (FileUtils.isMovingToTrashSupported() && FileUtils.moveToTrash(removedFile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// remove json files first to ensure HMCL will not recognize this folder as a valid version.
|
||||
|
||||
@@ -140,7 +140,7 @@ public final class CurseCompletionTask extends Task {
|
||||
for (CurseManifestFile file : newManifest.getFiles())
|
||||
if (StringUtils.isNotBlank(file.getFileName())) {
|
||||
if (!modManager.hasSimpleMod(file.getFileName())) {
|
||||
dependencies.add(new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile()));
|
||||
dependencies.add(new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile()).setCaching(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -119,7 +119,7 @@ public final class ForgeModMetadata {
|
||||
Path mcmod = fs.getPath("mcmod.info");
|
||||
if (Files.notExists(mcmod))
|
||||
throw new IOException("File " + modFile + " is not a Forge mod.");
|
||||
List<ForgeModMetadata> modList = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(mcmod)),
|
||||
List<ForgeModMetadata> modList = JsonUtils.GSON.fromJson(FileUtils.readText(mcmod),
|
||||
new TypeToken<List<ForgeModMetadata>>() {
|
||||
}.getType());
|
||||
if (modList == null || modList.isEmpty())
|
||||
|
||||
@@ -20,7 +20,9 @@ package org.jackhuang.hmcl.mod;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ImmediateBooleanProperty;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -42,7 +44,7 @@ public final class ModInfo implements Comparable<ModInfo> {
|
||||
private final String gameVersion;
|
||||
private final String url;
|
||||
private final String fileName;
|
||||
private final ImmediateBooleanProperty activeProperty;
|
||||
private final BooleanProperty activeProperty;
|
||||
|
||||
public ModInfo(ModManager modManager, File file, String name, String description) {
|
||||
this(modManager, file, name, description, "", "", "", "");
|
||||
@@ -57,7 +59,7 @@ public final class ModInfo implements Comparable<ModInfo> {
|
||||
this.gameVersion = gameVersion;
|
||||
this.url = url;
|
||||
|
||||
activeProperty = new ImmediateBooleanProperty(this, "active", !modManager.isDisabled(file)) {
|
||||
activeProperty = new SimpleBooleanProperty(this, "active", !modManager.isDisabled(file)) {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Path path = ModInfo.this.file.toAbsolutePath();
|
||||
@@ -104,7 +106,7 @@ public final class ModInfo implements Comparable<ModInfo> {
|
||||
return url;
|
||||
}
|
||||
|
||||
public ImmediateBooleanProperty activeProperty() {
|
||||
public BooleanProperty activeProperty() {
|
||||
return activeProperty;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.task.SilentException;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
@@ -49,11 +48,6 @@ public class ModpackUpdateTask extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return Collections.singleton(updateTask);
|
||||
@@ -78,8 +72,6 @@ public class ModpackUpdateTask extends Task {
|
||||
repository.removeVersionFromDisk(id);
|
||||
|
||||
FileUtils.copyDirectory(backupFolder, repository.getVersionRoot(id).toPath());
|
||||
|
||||
throw new SilentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.add(new MaintainTask(version).then(var -> new VersionJsonSaveTask(repository, var.get(MaintainTask.ID))));
|
||||
dependencies.add(new MaintainTask(version).thenCompose(maintainedVersion -> new VersionJsonSaveTask(repository, maintainedVersion)));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,57 +18,53 @@
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A task that combines two tasks and make sure [pred] runs before succ.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
final class CoupleTask<P extends Task> extends Task {
|
||||
final class CoupleTask extends Task {
|
||||
|
||||
private final boolean relyingOnDependents;
|
||||
private final Collection<Task> dependents;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ;
|
||||
private final Task pred;
|
||||
private Task succ;
|
||||
private final ExceptionalSupplier<Task, ?> supplier;
|
||||
|
||||
/**
|
||||
* A task that combines two tasks and make sure pred runs before succ.
|
||||
*
|
||||
* @param pred the task that runs before succ.
|
||||
* @param succ a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
|
||||
* @param pred the task that runs before supplier.
|
||||
* @param supplier a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
|
||||
* @param relyingOnDependents true if this task chain will be broken when task pred fails.
|
||||
*/
|
||||
public CoupleTask(P pred, ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ, boolean relyingOnDependents) {
|
||||
this.dependents = pred == null ? Collections.emptySet() : Collections.singleton(pred);
|
||||
this.succ = succ;
|
||||
CoupleTask(Task pred, ExceptionalSupplier<Task, ?> supplier, boolean relyingOnDependents) {
|
||||
this.pred = pred;
|
||||
this.supplier = supplier;
|
||||
this.relyingOnDependents = relyingOnDependents;
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
setName(succ.toString());
|
||||
setName(supplier.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
setName(succ.toString());
|
||||
Task task = succ.apply(getVariables());
|
||||
if (task != null)
|
||||
dependencies.add(task);
|
||||
setName(supplier.toString());
|
||||
succ = supplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return dependents;
|
||||
return pred == null ? Collections.emptySet() : Collections.singleton(pred);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependencies() {
|
||||
return dependencies;
|
||||
return succ == null ? Collections.emptySet() : Collections.singleton(succ);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.io.RandomAccessFile;
|
||||
import java.math.BigInteger;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Optional;
|
||||
@@ -212,7 +213,7 @@ public class FileDownloadTask extends Task {
|
||||
break;
|
||||
}
|
||||
|
||||
File temp = null;
|
||||
Path temp = null;
|
||||
|
||||
try {
|
||||
updateProgress(0);
|
||||
@@ -242,15 +243,15 @@ public class FileDownloadTask extends Task {
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Could not make directory " + file.getAbsoluteFile().getParent());
|
||||
|
||||
temp = FileUtils.createTempFile();
|
||||
rFile = new RandomAccessFile(temp, "rw");
|
||||
temp = Files.createTempFile(null, null);
|
||||
rFile = new RandomAccessFile(temp.toFile(), "rw");
|
||||
|
||||
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
||||
|
||||
stream = con.getInputStream();
|
||||
int lastDownloaded = 0, downloaded = 0;
|
||||
long lastTime = System.currentTimeMillis();
|
||||
byte buffer[] = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
while (true) {
|
||||
if (Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt();
|
||||
@@ -283,16 +284,15 @@ public class FileDownloadTask extends Task {
|
||||
|
||||
// Restore temp file to original name.
|
||||
if (Thread.interrupted()) {
|
||||
temp.delete();
|
||||
temp.toFile().delete();
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
} else {
|
||||
if (file.exists() && !file.delete())
|
||||
throw new IOException("Unable to delete existent file " + file);
|
||||
Files.deleteIfExists(file.toPath());
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Unable to make parent directory " + file);
|
||||
try {
|
||||
FileUtils.moveFile(temp, file);
|
||||
FileUtils.moveFile(temp.toFile(), file);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to move temp file from " + temp + " to " + file, e);
|
||||
}
|
||||
@@ -321,7 +321,7 @@ public class FileDownloadTask extends Task {
|
||||
return;
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
if (temp != null)
|
||||
temp.delete();
|
||||
temp.toFile().delete();
|
||||
exception = e;
|
||||
} finally {
|
||||
closeFiles();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
|
||||
public interface FinalizedCallback {
|
||||
void execute(AutoTypingMap<String> variables, boolean isDependentsSucceeded) throws Exception;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* A task that combines two tasks and make sure [pred] runs before succ.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
final class FinalizedTask extends Task {
|
||||
|
||||
private final Collection<Task> dependents;
|
||||
private final FinalizedCallback callback;
|
||||
private final Scheduler scheduler;
|
||||
|
||||
/**
|
||||
* A task that combines two tasks and make sure pred runs before succ.
|
||||
*
|
||||
* @param pred the task that runs before succ.
|
||||
* @param callback a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
|
||||
*/
|
||||
public FinalizedTask(Task pred, Scheduler scheduler, FinalizedCallback callback, String name) {
|
||||
this.dependents = Collections.singleton(pred);
|
||||
this.scheduler = scheduler;
|
||||
this.callback = callback;
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
callback.execute(getVariables(), isDependentsSucceeded());
|
||||
|
||||
if (!isDependentsSucceeded())
|
||||
throw new SilentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return dependents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependents() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +72,6 @@ public final class GetTask extends TaskResult<String> {
|
||||
return Schedulers.io();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public GetTask setCacheRepository(CacheRepository repository) {
|
||||
this.repository = repository;
|
||||
return this;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
/**
|
||||
* If a task throws {@link SilentException},
|
||||
* the task will be marked as failure but do not log the stacktrace.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class SilentException extends Exception {
|
||||
|
||||
}
|
||||
@@ -17,8 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -26,16 +25,16 @@ import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||
*/
|
||||
class SimpleTask extends Task {
|
||||
|
||||
private final ExceptionalConsumer<AutoTypingMap<String>, ?> consumer;
|
||||
private final ExceptionalRunnable<?> closure;
|
||||
private final Scheduler scheduler;
|
||||
|
||||
public SimpleTask(String name, ExceptionalConsumer<AutoTypingMap<String>, ?> consumer, Scheduler scheduler) {
|
||||
this.consumer = consumer;
|
||||
public SimpleTask(String name, ExceptionalRunnable<?> closure, Scheduler scheduler) {
|
||||
this.closure = closure;
|
||||
this.scheduler = scheduler;
|
||||
|
||||
if (name == null) {
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
setName(consumer.toString());
|
||||
setName(closure.toString());
|
||||
} else {
|
||||
setName(name);
|
||||
}
|
||||
@@ -48,6 +47,6 @@ class SimpleTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
consumer.accept(getVariables());
|
||||
closure.run();
|
||||
}
|
||||
}
|
||||
@@ -17,24 +17,30 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
|
||||
public final class SimpleTaskResult<V> extends TaskResult<V> {
|
||||
private final String id;
|
||||
private final ExceptionalSupplier<V, ?> supplier;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public SimpleTaskResult(String id, ExceptionalSupplier<V, ?> supplier) {
|
||||
this.id = id;
|
||||
this.supplier = supplier;
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class SimpleTaskResult<V> extends TaskResult<V> {
|
||||
|
||||
private final Callable<V> callable;
|
||||
|
||||
public SimpleTaskResult(Callable<V> callable) {
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
public SimpleTaskResult(ExceptionalSupplier<V, ?> supplier) {
|
||||
this.callable = supplier.toCallable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
setResult(supplier.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
setResult(callable.call());
|
||||
}
|
||||
}
|
||||
@@ -23,17 +23,17 @@ import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.ReflectionHelper;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
@@ -68,13 +68,13 @@ public abstract class Task {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
private Throwable lastException = null;
|
||||
private Exception lastException;
|
||||
|
||||
public Throwable getLastException() {
|
||||
public Exception getLastException() {
|
||||
return lastException;
|
||||
}
|
||||
|
||||
void setLastException(Throwable e) {
|
||||
void setLastException(Exception e) {
|
||||
lastException = e;
|
||||
}
|
||||
|
||||
@@ -134,16 +134,6 @@ public abstract class Task {
|
||||
return this;
|
||||
}
|
||||
|
||||
private AutoTypingMap<String> variables = null;
|
||||
|
||||
public AutoTypingMap<String> getVariables() {
|
||||
return variables;
|
||||
}
|
||||
|
||||
void setVariables(AutoTypingMap<String> variables) {
|
||||
this.variables = variables;
|
||||
}
|
||||
|
||||
public boolean doPreExecute() {
|
||||
return false;
|
||||
}
|
||||
@@ -165,8 +155,15 @@ public abstract class Task {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called after dependency tasks terminated all together.
|
||||
*
|
||||
* You can check whether dependencies succeed in this method by calling
|
||||
* {@link Task#isDependenciesSucceeded()} no matter when
|
||||
* {@link Task#isRelyingOnDependencies()} returns true or false.
|
||||
*
|
||||
* @throws InterruptedException if current thread is interrupted
|
||||
* @see Thread#isInterrupted
|
||||
* @see Task#isDependenciesSucceeded()
|
||||
*/
|
||||
public void postExecute() throws Exception {}
|
||||
|
||||
@@ -276,121 +273,198 @@ public abstract class Task {
|
||||
return executor().test();
|
||||
}
|
||||
|
||||
public final void subscribe(Task subscriber) {
|
||||
new TaskExecutor(then(subscriber)).start();
|
||||
}
|
||||
|
||||
public final void subscribe(Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
subscribe(of(scheduler, closure));
|
||||
}
|
||||
|
||||
public final void subscribe(Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
subscribe(of(scheduler, ExceptionalConsumer.fromRunnable(closure)));
|
||||
}
|
||||
|
||||
public final void subscribe(ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
subscribe(of(closure));
|
||||
}
|
||||
|
||||
public final void subscribe(ExceptionalRunnable<?> closure) {
|
||||
subscribe(of(closure));
|
||||
}
|
||||
|
||||
public final Task then(Task b) {
|
||||
return then(convert(b));
|
||||
}
|
||||
|
||||
public final Task then(ExceptionalFunction<AutoTypingMap<String>, Task, ?> b) {
|
||||
return new CoupleTask<>(this, b, true);
|
||||
public final Task then(ExceptionalSupplier<Task, ?> b) {
|
||||
return new CoupleTask(this, b, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new TaskResult that, when this task completes
|
||||
* normally, is executed using the default Scheduler.
|
||||
*
|
||||
* @param fn the function to use to compute the value of the returned TaskResult
|
||||
* @param <U> the function's return type
|
||||
* @return the new TaskResult
|
||||
*/
|
||||
public final <U> TaskResult<U> thenSupply(Callable<U> fn) {
|
||||
return thenCompose(() -> Task.ofResult(fn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new TaskResult that, when this task completes
|
||||
* normally, is executed.
|
||||
*
|
||||
* @param fn the function returning a new TaskResult
|
||||
* @param <U> the type of the returned TaskResult's result
|
||||
* @return the TaskResult
|
||||
*/
|
||||
public final <U> TaskResult<U> thenCompose(ExceptionalSupplier<TaskResult<U>, ?> fn) {
|
||||
return new TaskResult<U>() {
|
||||
TaskResult<U> then;
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return Collections.singleton(Task.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
then = fn.get().storeTo(this::setResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return then == null ? Collections.emptyList() : Collections.singleton(then);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public final Task with(Task b) {
|
||||
return with(convert(b));
|
||||
}
|
||||
|
||||
public final <E extends Exception> Task with(ExceptionalFunction<AutoTypingMap<String>, Task, E> b) {
|
||||
return new CoupleTask<>(this, b, false);
|
||||
public final <E extends Exception> Task with(ExceptionalSupplier<Task, E> b) {
|
||||
return new CoupleTask(this, b, false);
|
||||
}
|
||||
|
||||
public final Task finalized(FinalizedCallback b) {
|
||||
return finalized(Schedulers.defaultScheduler(), b);
|
||||
/**
|
||||
* Returns a new Task with the same exception as this task, that executes
|
||||
* the given action when this task completes.
|
||||
*
|
||||
* <p>When this task is complete, the given action is invoked, a boolean
|
||||
* value represents the execution status of this task, and the exception
|
||||
* (or {@code null} if none) of this task as arguments. The returned task
|
||||
* is completed when the action returns. If the supplied action itself
|
||||
* encounters an exception, then the returned task exceptionally completes
|
||||
* with this exception unless this task also completed exceptionally.
|
||||
*
|
||||
* @param action the action to perform
|
||||
* @return the new Task
|
||||
*/
|
||||
public final Task whenComplete(FinalizedCallback action) {
|
||||
return whenComplete(Schedulers.defaultScheduler(), action);
|
||||
}
|
||||
|
||||
public final Task finalized(Scheduler scheduler, FinalizedCallback b) {
|
||||
return new FinalizedTask(this, scheduler, b, ReflectionHelper.getCaller().toString());
|
||||
/**
|
||||
* Returns a new Task with the same exception as this task, that executes
|
||||
* the given action when this task completes.
|
||||
*
|
||||
* <p>When this task is complete, the given action is invoked, a boolean
|
||||
* value represents the execution status of this task, and the exception
|
||||
* (or {@code null} if none, which means when isDependentSucceeded is false,
|
||||
* exception may be null) of this task as arguments. The returned task
|
||||
* is completed when the action returns. If the supplied action itself
|
||||
* encounters an exception, then the returned task exceptionally completes
|
||||
* with this exception unless this task also completed exceptionally.
|
||||
*
|
||||
* @param action the action to perform
|
||||
* @param scheduler the executor to use for asynchronous execution
|
||||
* @return the new Task
|
||||
*/
|
||||
public final Task whenComplete(Scheduler scheduler, FinalizedCallback action) {
|
||||
return new Task() {
|
||||
{
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
action.execute(isDependentsSucceeded(), Task.this.getLastException());
|
||||
|
||||
if (!isDependentsSucceeded()) {
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
if (Task.this.getLastException() == null)
|
||||
throw new CancellationException();
|
||||
else
|
||||
throw Task.this.getLastException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(Task.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependents() {
|
||||
return false;
|
||||
}
|
||||
}.setName(getCaller());
|
||||
}
|
||||
|
||||
public final <T extends Exception, K extends Exception> Task finalized(Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, T> success, ExceptionalConsumer<Exception, K> failure) {
|
||||
return finalized(scheduler, (variables, isDependentsSucceeded) -> {
|
||||
if (isDependentsSucceeded) {
|
||||
/**
|
||||
* Returns a new Task with the same exception as this task, that executes
|
||||
* the given actions when this task completes.
|
||||
*
|
||||
* <p>When this task is complete, the given success action is invoked, the
|
||||
* given failure action is invoked with the exception of this task. The
|
||||
* returned task is completed when the action returns. If the supplied
|
||||
* action itself encounters an exception, then the returned task exceptionally
|
||||
* completes with this exception unless this task also
|
||||
* completed exceptionally.
|
||||
*
|
||||
* @param success the action to perform when this task successfully completed
|
||||
* @param failure the action to perform when this task exceptionally returned
|
||||
* @return the new Task
|
||||
*/
|
||||
public final <E1 extends Exception, E2 extends Exception> Task whenComplete(Scheduler scheduler, ExceptionalRunnable<E1> success, ExceptionalConsumer<Exception, E2> failure) {
|
||||
return whenComplete(scheduler, (isDependentSucceeded, exception) -> {
|
||||
if (isDependentSucceeded) {
|
||||
if (success != null)
|
||||
try {
|
||||
success.accept(variables);
|
||||
success.run();
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to execute " + success, e);
|
||||
if (failure != null)
|
||||
failure.accept(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (failure != null)
|
||||
failure.accept(variables.get(TaskExecutor.LAST_EXCEPTION_ID));
|
||||
failure.accept(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task of(String name, ExceptionalRunnable<?> runnable) {
|
||||
return of(name, ExceptionalConsumer.fromRunnable(runnable));
|
||||
}
|
||||
|
||||
public static Task of(ExceptionalRunnable<?> runnable) {
|
||||
return of(ExceptionalConsumer.fromRunnable(runnable));
|
||||
}
|
||||
|
||||
public static Task of(String name, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return of(name, Schedulers.defaultScheduler(), closure);
|
||||
}
|
||||
|
||||
public static Task of(ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
public static Task of(ExceptionalRunnable<?> closure) {
|
||||
return of(Schedulers.defaultScheduler(), closure);
|
||||
}
|
||||
|
||||
public static Task of(String name, Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return new SimpleTask(name, closure, scheduler);
|
||||
}
|
||||
|
||||
public static Task of(Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
|
||||
return of(ReflectionHelper.getCaller().toString(), scheduler, closure);
|
||||
}
|
||||
|
||||
public static Task of(String name, Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
return new SimpleTask(name, ExceptionalConsumer.fromRunnable(closure), scheduler);
|
||||
public static Task of(String name, ExceptionalRunnable<?> closure) {
|
||||
return of(name, Schedulers.defaultScheduler(), closure);
|
||||
}
|
||||
|
||||
public static Task of(Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
return of(ReflectionHelper.getCaller().toString(), scheduler, closure);
|
||||
return of(getCaller(), scheduler, closure);
|
||||
}
|
||||
|
||||
public static Task ofThen(ExceptionalFunction<AutoTypingMap<String>, Task, ?> b) {
|
||||
return new CoupleTask<>(null, b, true);
|
||||
public static Task of(String name, Scheduler scheduler, ExceptionalRunnable<?> closure) {
|
||||
return new SimpleTask(name, closure, scheduler);
|
||||
}
|
||||
|
||||
public static Task ofThen(ExceptionalSupplier<Task, ?> b) {
|
||||
return new CoupleTask(null, b, true);
|
||||
}
|
||||
|
||||
public static <V> TaskResult<V> ofResult(Callable<V> callable) {
|
||||
return ofResult("", callable);
|
||||
return ofResult(getCaller(), callable);
|
||||
}
|
||||
|
||||
public static <V> TaskResult<V> ofResult(String id, Callable<V> callable) {
|
||||
return new TaskCallable<>(id, callable);
|
||||
public static <V> TaskResult<V> ofResult(String name, Callable<V> callable) {
|
||||
return new SimpleTaskResult<>(callable).setName(name);
|
||||
}
|
||||
|
||||
public static <V> TaskResult<V> ofResult(String id, ExceptionalFunction<AutoTypingMap<String>, V, ?> closure) {
|
||||
return new TaskCallable<>(id, closure);
|
||||
}
|
||||
|
||||
private static ExceptionalFunction<AutoTypingMap<String>, Task, ?> convert(Task t) {
|
||||
return new ExceptionalFunction<AutoTypingMap<String>, Task, Exception>() {
|
||||
private static ExceptionalSupplier<Task, ?> convert(Task t) {
|
||||
return new ExceptionalSupplier<Task, Exception>() {
|
||||
@Override
|
||||
public Task apply(AutoTypingMap<String> autoTypingMap) {
|
||||
public Task get() {
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -422,4 +496,12 @@ public abstract class Task {
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
public interface FinalizedCallback {
|
||||
void execute(boolean isDependentSucceeded, Exception exception) throws Exception;
|
||||
}
|
||||
|
||||
static String getCaller() {
|
||||
return ReflectionHelper.getCaller(packageName -> !"org.jackhuang.hmcl.task".equals(packageName)).toString();
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class TaskCallable<V> extends TaskResult<V> {
|
||||
|
||||
private final String id;
|
||||
private final ExceptionalFunction<AutoTypingMap<String>, V, ?> callable;
|
||||
|
||||
public TaskCallable(String id, Callable<V> callable) {
|
||||
this(id, variables -> callable.call());
|
||||
}
|
||||
|
||||
public TaskCallable(String id, ExceptionalFunction<AutoTypingMap<String>, V, ?> callable) {
|
||||
this.id = id;
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
setResult(callable.apply(getVariables()));
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,6 @@ public final class TaskExecutor {
|
||||
private Exception lastException;
|
||||
private final AtomicInteger totTask = new AtomicInteger(0);
|
||||
private final ConcurrentLinkedQueue<Future<?>> workerQueue = new ConcurrentLinkedQueue<>();
|
||||
private final AutoTypingMap<String> variables = new AutoTypingMap<>(new HashMap<>());
|
||||
private Scheduler scheduler = Schedulers.newThread();
|
||||
|
||||
public TaskExecutor(Task task) {
|
||||
@@ -134,6 +133,7 @@ public final class TaskExecutor {
|
||||
private boolean executeTask(Task task) {
|
||||
if (canceled) {
|
||||
task.setState(Task.TaskState.FAILED);
|
||||
task.setLastException(new CancellationException());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -147,8 +147,6 @@ public final class TaskExecutor {
|
||||
boolean flag = false;
|
||||
|
||||
try {
|
||||
task.setVariables(variables);
|
||||
|
||||
if (task.doPreExecute()) {
|
||||
try {
|
||||
task.getScheduler().schedule(task::preExecute).get();
|
||||
@@ -160,9 +158,13 @@ public final class TaskExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
boolean doDependentsSucceeded = executeTasks(task.getDependents());
|
||||
if (!doDependentsSucceeded && task.isRelyingOnDependents() || canceled)
|
||||
throw new SilentException();
|
||||
Collection<? extends Task> dependents = task.getDependents();
|
||||
boolean doDependentsSucceeded = executeTasks(dependents);
|
||||
Exception dependentsException = dependents.stream().map(Task::getLastException).filter(Objects::nonNull).findAny().orElse(null);
|
||||
if (!doDependentsSucceeded && task.isRelyingOnDependents() || canceled) {
|
||||
task.setLastException(dependentsException);
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
if (doDependentsSucceeded)
|
||||
task.setDependentsSucceeded();
|
||||
@@ -182,16 +184,9 @@ public final class TaskExecutor {
|
||||
task.setState(Task.TaskState.EXECUTED);
|
||||
}
|
||||
|
||||
if (task instanceof TaskResult<?>) {
|
||||
TaskResult<?> taskResult = (TaskResult<?>) task;
|
||||
variables.set(taskResult.getId(), taskResult.getResult());
|
||||
}
|
||||
|
||||
boolean doDependenciesSucceeded = executeTasks(task.getDependencies());
|
||||
if (!doDependenciesSucceeded && task.isRelyingOnDependencies()) {
|
||||
Logging.LOG.severe("Subtasks failed for " + task.getName());
|
||||
throw new SilentException();
|
||||
}
|
||||
Collection<? extends Task> dependencies = task.getDependencies();
|
||||
boolean doDependenciesSucceeded = executeTasks(dependencies);
|
||||
Exception dependenciesException = dependencies.stream().map(Task::getLastException).filter(Objects::nonNull).findAny().orElse(null);
|
||||
|
||||
if (doDependenciesSucceeded)
|
||||
task.setDependenciesSucceeded();
|
||||
@@ -207,6 +202,12 @@ public final class TaskExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
if (!doDependenciesSucceeded && task.isRelyingOnDependencies()) {
|
||||
Logging.LOG.severe("Subtasks failed for " + task.getName());
|
||||
task.setLastException(dependenciesException);
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
flag = true;
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINER, "Task finished: " + task.getName());
|
||||
@@ -221,19 +222,17 @@ public final class TaskExecutor {
|
||||
}
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
} catch (SilentException | RejectedExecutionException e) {
|
||||
// do nothing
|
||||
} catch (CancellationException | RejectedExecutionException e) {
|
||||
if (task.getLastException() == null)
|
||||
task.setLastException(e);
|
||||
} catch (Exception e) {
|
||||
task.setLastException(e);
|
||||
lastException = e;
|
||||
variables.set(LAST_EXCEPTION_ID, e);
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
}
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
} finally {
|
||||
task.setVariables(null);
|
||||
}
|
||||
task.setState(flag ? Task.TaskState.SUCCEEDED : Task.TaskState.FAILED);
|
||||
return flag;
|
||||
@@ -243,10 +242,6 @@ public final class TaskExecutor {
|
||||
return totTask.get();
|
||||
}
|
||||
|
||||
public AutoTypingMap<String> getVariables() {
|
||||
return variables;
|
||||
}
|
||||
|
||||
private class Invoker implements ExceptionalRunnable<Exception> {
|
||||
|
||||
private final Task task;
|
||||
@@ -271,6 +266,4 @@ public final class TaskExecutor {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final String LAST_EXCEPTION_ID = "lastException";
|
||||
}
|
||||
@@ -17,63 +17,212 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.ReflectionHelper;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A task that has a result.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class TaskResult<V> extends Task {
|
||||
public abstract class TaskResult<T> extends Task {
|
||||
|
||||
private V result;
|
||||
private T result;
|
||||
private Consumer<T> resultConsumer;
|
||||
|
||||
public V getResult() {
|
||||
@Override
|
||||
public TaskResult<T> setName(String name) {
|
||||
super.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of this task.
|
||||
*
|
||||
* The result will be generated only if the execution is completed.
|
||||
*/
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(V result) {
|
||||
protected void setResult(T result) {
|
||||
this.result = result;
|
||||
if (resultConsumer != null)
|
||||
resultConsumer.accept(result);
|
||||
}
|
||||
|
||||
public abstract String getId();
|
||||
|
||||
public <R, E extends Exception> TaskResult<R> thenResult(ExceptionalFunction<V, R, E> task) {
|
||||
return thenResult(Schedulers.defaultScheduler(), task);
|
||||
/**
|
||||
* Sync the result of this task by given action.
|
||||
*
|
||||
* @param action the action to perform when result of this task changed
|
||||
* @return this TaskResult
|
||||
*/
|
||||
public TaskResult<T> storeTo(Consumer<T> action) {
|
||||
this.resultConsumer = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public <R, E extends Exception> TaskResult<R> thenResult(Scheduler scheduler, ExceptionalFunction<V, R, E> task) {
|
||||
return thenResult(ReflectionHelper.getCaller().toString(), scheduler, task);
|
||||
/**
|
||||
* Returns a new TaskResult that, when this task completes
|
||||
* normally, is executed with result of this task as the argument
|
||||
* to the supplied function.
|
||||
*
|
||||
* @param fn the function returning a new TaskResult
|
||||
* @param <U> the type of the returned TaskResult's result
|
||||
* @return the TaskResult
|
||||
*/
|
||||
public <U, E extends Exception> TaskResult<U> thenCompose(ExceptionalFunction<T, TaskResult<U>, E> fn) {
|
||||
return new TaskResult<U>() {
|
||||
TaskResult<U> then;
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return Collections.singleton(TaskResult.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
then = fn.apply(TaskResult.this.getResult()).storeTo(this::setResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return then == null ? Collections.emptyList() : Collections.singleton(then);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public <R, E extends Exception> TaskResult<R> thenResult(String id, Scheduler scheduler, ExceptionalFunction<V, R, E> task) {
|
||||
return new Subtask<>(id, scheduler, task);
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed with this task as the argument
|
||||
* to the supplied function.
|
||||
*
|
||||
* @param fn the function returning a new Task
|
||||
* @return the Task
|
||||
*/
|
||||
public <E extends Exception> Task then(ExceptionalFunction<T, Task, E> fn) {
|
||||
return new CoupleTask(this, () -> fn.apply(getResult()), true);
|
||||
}
|
||||
|
||||
public <T extends Exception, K extends Exception> Task finalizedResult(Scheduler scheduler, ExceptionalConsumer<V, T> success, ExceptionalConsumer<Exception, K> failure) {
|
||||
return finalized(scheduler, variables -> success.accept(getResult()), failure);
|
||||
/**
|
||||
* Returns a new TaskResult that, when this task completes
|
||||
* normally, is executed using the default Scheduler, with this
|
||||
* task's result as the argument to the supplied function.
|
||||
*
|
||||
* @param fn the function to use to compute the value of the returned TaskResult
|
||||
* @param <U> the function's return type
|
||||
* @return the new TaskResult
|
||||
*/
|
||||
public <U, E extends Exception> TaskResult<U> thenApply(ExceptionalFunction<T, U, E> fn) {
|
||||
return thenApply(Schedulers.defaultScheduler(), fn);
|
||||
}
|
||||
|
||||
public Task finalizedResult(Scheduler scheduler, FinalizedCallback<V> callback) {
|
||||
return new FinalizedTask(this, scheduler,
|
||||
(variables, isDependentsSucceeded) -> callback.execute(getResult(), isDependentsSucceeded),
|
||||
ReflectionHelper.getCaller().toString());
|
||||
/**
|
||||
* Returns a new TaskResult that, when this task completes
|
||||
* normally, is executed using the supplied Scheduler, with this
|
||||
* task's result as the argument to the supplied function.
|
||||
*
|
||||
* @param scheduler the executor to use for asynchronous execution
|
||||
* @param fn the function to use to compute the value of the returned TaskResult
|
||||
* @param <U> the function's return type
|
||||
* @return the new TaskResult
|
||||
*/
|
||||
public <U, E extends Exception> TaskResult<U> thenApply(Scheduler scheduler, ExceptionalFunction<T, U, E> fn) {
|
||||
return thenApply(getCaller(), scheduler, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new TaskResult that, when this task completes
|
||||
* normally, is executed using the supplied Scheduler, with this
|
||||
* task's result as the argument to the supplied function.
|
||||
*
|
||||
* @param name the name of this new TaskResult for displaying
|
||||
* @param scheduler the executor to use for asynchronous execution
|
||||
* @param fn the function to use to compute the value of the returned TaskResult
|
||||
* @param <U> the function's return type
|
||||
* @return the new TaskResult
|
||||
*/
|
||||
public <U, E extends Exception> TaskResult<U> thenApply(String name, Scheduler scheduler, ExceptionalFunction<T, U, E> fn) {
|
||||
return new Subtask<>(name, scheduler, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed using the default Scheduler, with this
|
||||
* task's result as the argument to the supplied action.
|
||||
*
|
||||
* @param action the action to perform before completing the
|
||||
* returned Task
|
||||
* @return the new Task
|
||||
*/
|
||||
public <E extends Exception> Task thenAccept(ExceptionalConsumer<T, E> action) {
|
||||
return thenAccept(Schedulers.defaultScheduler(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed using the supplied Scheduler, with this
|
||||
* task's result as the argument to the supplied action.
|
||||
*
|
||||
* @param action the action to perform before completing the returned Task
|
||||
* @param scheduler the executor to use for asynchronous execution
|
||||
* @return the new Task
|
||||
*/
|
||||
public <E extends Exception> Task thenAccept(Scheduler scheduler, ExceptionalConsumer<T, E> action) {
|
||||
return new CoupleTask(this, () -> Task.of(scheduler, () -> action.accept(getResult())), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task with the same exception as this task, that executes
|
||||
* the given actions when this task completes.
|
||||
*
|
||||
* <p>When this task is complete, the given success action is invoked with
|
||||
* the result, the given failure action is invoked with the exception of
|
||||
* this task. The returned task is completed when the action returns. If
|
||||
* the supplied action itself encounters an exception, then the returned
|
||||
* task exceptionally completes with this exception unless this task also
|
||||
* completed exceptionally.
|
||||
*
|
||||
* @param success the action to perform when this task successfully completed
|
||||
* @param failure the action to perform when this task exceptionally returned
|
||||
* @return the new Task
|
||||
*/
|
||||
public <E1 extends Exception, E2 extends Exception> Task whenComplete(Scheduler scheduler, ExceptionalConsumer<T, E1> success, ExceptionalConsumer<Exception, E2> failure) {
|
||||
return whenComplete(scheduler, () -> success.accept(getResult()), failure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task with the same exception as this task, that executes
|
||||
* the given action when this task completes.
|
||||
*
|
||||
* <p>When this task is complete, the given action is invoked with the
|
||||
* result (or {@code null} if none), a boolean value represents the
|
||||
* execution status of this task, and the exception (or {@code null}
|
||||
* if none) of this task as arguments. The returned task is completed
|
||||
* when the action returns. If the supplied action itself encounters an
|
||||
* exception, then the returned task exceptionally completes with this
|
||||
* exception unless this task also completed exceptionally.
|
||||
*
|
||||
* @param action the action to perform
|
||||
* @return the new Task
|
||||
*/
|
||||
public Task whenComplete(Scheduler scheduler, FinalizedCallback<T> action) {
|
||||
return whenComplete(scheduler, ((isDependentSucceeded, exception) -> action.execute(getResult(), isDependentSucceeded, exception)));
|
||||
}
|
||||
|
||||
private class Subtask<R> extends TaskResult<R> {
|
||||
private final String id;
|
||||
private final Scheduler scheduler;
|
||||
private final ExceptionalFunction<V, R, ?> callable;
|
||||
private final ExceptionalFunction<T, R, ?> callable;
|
||||
|
||||
public Subtask(String id, Scheduler scheduler, ExceptionalFunction<V, R, ?> callable) {
|
||||
this.id = id;
|
||||
public Subtask(String name, Scheduler scheduler, ExceptionalFunction<T, R, ?> callable) {
|
||||
this.scheduler = scheduler;
|
||||
this.callable = callable;
|
||||
|
||||
setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,11 +230,6 @@ public abstract class TaskResult<V> extends Task {
|
||||
return Collections.singleton(TaskResult.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scheduler getScheduler() {
|
||||
return scheduler;
|
||||
@@ -98,6 +242,6 @@ public abstract class TaskResult<V> extends Task {
|
||||
}
|
||||
|
||||
public interface FinalizedCallback<V> {
|
||||
void execute(V result, boolean isDependentsSucceeded) throws Exception;
|
||||
void execute(V result, boolean isDependentSucceeded, Exception exception) throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
|
||||
import java.util.*;
|
||||
@@ -26,6 +25,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -76,13 +76,6 @@ public final class Lang {
|
||||
}
|
||||
}
|
||||
|
||||
public static void ignoringException(ExceptionalRunnable<?> runnable) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast {@code obj} to V dynamically.
|
||||
* @param obj the object reference to be cast.
|
||||
@@ -98,20 +91,6 @@ public final class Lang {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element at the specific position {@code index} in {@code list}.
|
||||
*
|
||||
* @param index the index of element to be return
|
||||
* @param <V> the type of elements in {@code list}
|
||||
* @return the element at the specific position, null if index is out of bound.
|
||||
*/
|
||||
public static <V> Optional<V> get(List<V> list, int index) {
|
||||
if (index < 0 || index >= list.size())
|
||||
return Optional.empty();
|
||||
else
|
||||
return Optional.ofNullable(list.get(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two collections into one list.
|
||||
*
|
||||
@@ -220,4 +199,18 @@ public final class Lang {
|
||||
consumer.accept(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a useful function to prevent exceptions being eaten when using CompletableFuture.
|
||||
* You can write:
|
||||
* ... .exceptionally(handleUncaught);
|
||||
*/
|
||||
public static final Function<Throwable, Void> handleUncaught = e -> {
|
||||
handleUncaughtException(e);
|
||||
return null;
|
||||
};
|
||||
|
||||
public static void handleUncaughtException(Throwable e) {
|
||||
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
|
||||
}
|
||||
}
|
||||
@@ -17,17 +17,28 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ReflectionHelper {
|
||||
|
||||
public static StackTraceElement getCaller() {
|
||||
/**
|
||||
* Get caller, this method is caller sensitive.
|
||||
* @param packageFilter returns false if we consider the given package is internal calls, not the caller
|
||||
* @return the caller, method name, source file, line number
|
||||
*/
|
||||
public static StackTraceElement getCaller(Predicate<String> packageFilter) {
|
||||
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
|
||||
// element[0] is Thread.currentThread().getStackTrace()
|
||||
// element[1] is ReflectionHelper.getCaller(packageFilter)
|
||||
// so element[2] is caller of this method.
|
||||
StackTraceElement caller = elements[2];
|
||||
for (int i = 3; i < elements.length; ++i) {
|
||||
if (!caller.getClassName().equals(elements[i].getClassName()))
|
||||
if (packageFilter.test(StringUtils.substringBeforeLast(elements[i].getClassName(), '.')) &&
|
||||
!caller.getClassName().equals(elements[i].getClassName()))
|
||||
return elements[i];
|
||||
}
|
||||
return caller;
|
||||
|
||||
@@ -35,6 +35,7 @@ public interface Validation {
|
||||
* Throw an exception when values are malformed.
|
||||
*
|
||||
* @throws JsonParseException if fields are filled in wrong format or wrong type.
|
||||
* @throws TolerableValidationException if we want to replace this object with null (i.e. the object does not fulfill the constraints).
|
||||
*/
|
||||
void validate() throws JsonParseException, TolerableValidationException;
|
||||
}
|
||||
@@ -116,14 +116,43 @@ public final class FileUtils {
|
||||
return new String(Files.readAllBytes(file), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write plain text to file. Characters are encoded into bytes using UTF-8.
|
||||
*
|
||||
* We don't care about platform difference of line separator. Because readText accept all possibilities of line separator.
|
||||
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
|
||||
* All characters in text will be written into the file in binary format. Existing data will be erased.
|
||||
* @param file the path to the file
|
||||
* @param text the text being written to file
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void writeText(File file, String text) throws IOException {
|
||||
writeText(file, text, UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write plain text to file.
|
||||
*
|
||||
* We don't care about platform difference of line separator. Because readText accept all possibilities of line separator.
|
||||
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
|
||||
* All characters in text will be written into the file in binary format. Existing data will be erased.
|
||||
* @param file the path to the file
|
||||
* @param text the text being written to file
|
||||
* @param charset the charset to use for encoding
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void writeText(File file, String text, Charset charset) throws IOException {
|
||||
writeBytes(file, text.getBytes(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write byte array to file.
|
||||
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
|
||||
* All bytes in byte array will be written into the file in binary format. Existing data will be erased.
|
||||
* @param file the path to the file
|
||||
* @param array the data being written to file
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void writeBytes(File file, byte[] array) throws IOException {
|
||||
Files.createDirectories(file.toPath().getParent());
|
||||
Files.write(file.toPath(), array);
|
||||
@@ -153,6 +182,14 @@ public final class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy directory.
|
||||
* Paths of all files relative to source directory will be the same as the ones relative to destination directory.
|
||||
*
|
||||
* @param src the source directory.
|
||||
* @param dest the destination directory, which will be created if not existing.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static void copyDirectory(Path src, Path dest) throws IOException {
|
||||
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
|
||||
@Override
|
||||
@@ -173,6 +210,20 @@ public final class FileUtils {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Move file to trash.
|
||||
*
|
||||
* This method is only implemented in Java 9. Please check we are using Java 9 by invoking isMovingToTrashSupported.
|
||||
* Example:
|
||||
* <pre>{@code
|
||||
* if (FileUtils.isMovingToTrashSupported()) {
|
||||
* FileUtils.moveToTrash(file);
|
||||
* }
|
||||
* }</pre>
|
||||
* @param file the file being moved to trash.
|
||||
* @see FileUtils#isMovingToTrashSupported()
|
||||
* @return false if moveToTrash does not exist, or platform does not support Desktop.Action.MOVE_TO_TRASH
|
||||
*/
|
||||
public static boolean moveToTrash(File file) {
|
||||
try {
|
||||
java.awt.Desktop desktop = java.awt.Desktop.getDesktop();
|
||||
@@ -314,20 +365,4 @@ public final class FileUtils {
|
||||
result.add(it);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static File createTempFile() throws IOException {
|
||||
return createTempFile("tmp");
|
||||
}
|
||||
|
||||
public static File createTempFile(String prefix) throws IOException {
|
||||
return createTempFile(prefix, null);
|
||||
}
|
||||
|
||||
public static File createTempFile(String prefix, String suffix) throws IOException {
|
||||
return createTempFile(prefix, suffix, null);
|
||||
}
|
||||
|
||||
public static File createTempFile(String prefix, String suffix, File directory) throws IOException {
|
||||
return File.createTempFile(prefix, suffix, directory);
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,9 @@ import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* This utility class consists of some util methods operating on InputStream/OutputStream.
|
||||
*
|
||||
* @author huang
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class IOUtils {
|
||||
|
||||
@@ -31,12 +32,26 @@ public final class IOUtils {
|
||||
|
||||
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
/**
|
||||
* Read all bytes to a buffer from given input stream. The stream will not be closed.
|
||||
*
|
||||
* @param stream the InputStream being read.
|
||||
* @return all bytes read from the stream
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static byte[] readFullyWithoutClosing(InputStream stream) throws IOException {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
copyTo(stream, result);
|
||||
return result.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all bytes to a buffer from given input stream, and close the input stream finally.
|
||||
*
|
||||
* @param stream the InputStream being read, closed finally.
|
||||
* @return all bytes read from the stream
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static ByteArrayOutputStream readFully(InputStream stream) throws IOException {
|
||||
try (InputStream is = stream) {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
|
||||
+84
-44
@@ -18,51 +18,61 @@
|
||||
package org.jackhuang.hmcl.util.javafx;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.Lang.handleUncaughtException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
*/
|
||||
public abstract class MultiStepBinding<T, U> extends ObjectBinding<U> {
|
||||
public abstract class BindingMapping<T, U> extends ObjectBinding<U> {
|
||||
|
||||
public static <T> MultiStepBinding<?, T> of(ObservableValue<T> property) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> BindingMapping<?, T> of(ObservableValue<T> property) {
|
||||
if (property instanceof BindingMapping) {
|
||||
return (BindingMapping<?, T>) property;
|
||||
}
|
||||
return new SimpleBinding<>(property);
|
||||
}
|
||||
|
||||
public static <S extends Observable, T> BindingMapping<?, T> of(S watched, Function<S, T> mapper) {
|
||||
return of(Bindings.createObjectBinding(() -> mapper.apply(watched), watched));
|
||||
}
|
||||
|
||||
protected final ObservableValue<T> predecessor;
|
||||
|
||||
public MultiStepBinding(ObservableValue<T> predecessor) {
|
||||
public BindingMapping(ObservableValue<T> predecessor) {
|
||||
this.predecessor = requireNonNull(predecessor);
|
||||
bind(predecessor);
|
||||
}
|
||||
|
||||
public <V> MultiStepBinding<?, V> map(Function<U, V> mapper) {
|
||||
public <V> BindingMapping<?, V> map(Function<U, V> mapper) {
|
||||
return new MappedBinding<>(this, mapper);
|
||||
}
|
||||
|
||||
public <V> MultiStepBinding<?, V> flatMap(Function<U, ? extends ObservableValue<V>> mapper) {
|
||||
public <V> BindingMapping<?, V> flatMap(Function<U, ? extends ObservableValue<V>> mapper) {
|
||||
return flatMap(mapper, null);
|
||||
}
|
||||
|
||||
public <V> MultiStepBinding<?, V> flatMap(Function<U, ? extends ObservableValue<V>> mapper, Supplier<V> nullAlternative) {
|
||||
public <V> BindingMapping<?, V> flatMap(Function<U, ? extends ObservableValue<V>> mapper, Supplier<V> nullAlternative) {
|
||||
return new FlatMappedBinding<>(map(mapper), nullAlternative);
|
||||
}
|
||||
|
||||
public <V> MultiStepBinding<?, V> asyncMap(Function<U, V> mapper, V initial, Executor executor) {
|
||||
return new AsyncMappedBinding<>(this, mapper, executor, initial);
|
||||
public <V> BindingMapping<?, V> asyncMap(Function<U, CompletableFuture<V>> mapper, V initial) {
|
||||
return new AsyncMappedBinding<>(this, mapper, initial);
|
||||
}
|
||||
|
||||
private static class SimpleBinding<T> extends MultiStepBinding<T, T> {
|
||||
private static class SimpleBinding<T> extends BindingMapping<T, T> {
|
||||
|
||||
public SimpleBinding(ObservableValue<T> predecessor) {
|
||||
super(predecessor);
|
||||
@@ -74,17 +84,17 @@ public abstract class MultiStepBinding<T, U> extends ObjectBinding<U> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> MultiStepBinding<?, V> map(Function<T, V> mapper) {
|
||||
public <V> BindingMapping<?, V> map(Function<T, V> mapper) {
|
||||
return new MappedBinding<>(predecessor, mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> MultiStepBinding<?, V> asyncMap(Function<T, V> mapper, V initial, Executor executor) {
|
||||
return new AsyncMappedBinding<>(predecessor, mapper, executor, initial);
|
||||
public <V> BindingMapping<?, V> asyncMap(Function<T, CompletableFuture<V>> mapper, V initial) {
|
||||
return new AsyncMappedBinding<>(predecessor, mapper, initial);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MappedBinding<T, U> extends MultiStepBinding<T, U> {
|
||||
private static class MappedBinding<T, U> extends BindingMapping<T, U> {
|
||||
|
||||
private final Function<T, U> mapper;
|
||||
|
||||
@@ -99,7 +109,7 @@ public abstract class MultiStepBinding<T, U> extends ObjectBinding<U> {
|
||||
}
|
||||
}
|
||||
|
||||
private static class FlatMappedBinding<T extends ObservableValue<U>, U> extends MultiStepBinding<T, U> {
|
||||
private static class FlatMappedBinding<T extends ObservableValue<U>, U> extends BindingMapping<T, U> {
|
||||
|
||||
private final Supplier<U> nullAlternative;
|
||||
private T lastObservable = null;
|
||||
@@ -134,51 +144,81 @@ public abstract class MultiStepBinding<T, U> extends ObjectBinding<U> {
|
||||
}
|
||||
}
|
||||
|
||||
private static class AsyncMappedBinding<T, U> extends MultiStepBinding<T, U> {
|
||||
|
||||
private final InvocationDispatcher<T> dispatcher;
|
||||
private static class AsyncMappedBinding<T, U> extends BindingMapping<T, U> {
|
||||
|
||||
private boolean initialized = false;
|
||||
private T prev;
|
||||
private U value;
|
||||
|
||||
public AsyncMappedBinding(ObservableValue<T> predecessor, Function<T, U> mapper, Executor executor, U initial) {
|
||||
private final Function<T, CompletableFuture<U>> mapper;
|
||||
private T computingPrev;
|
||||
private boolean computing = false;
|
||||
|
||||
public AsyncMappedBinding(ObservableValue<T> predecessor, Function<T, CompletableFuture<U>> mapper, U initial) {
|
||||
super(predecessor);
|
||||
this.value = initial;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
dispatcher = InvocationDispatcher.runOn(executor, arg -> {
|
||||
synchronized (this) {
|
||||
if (initialized && Objects.equals(arg, prev)) {
|
||||
return;
|
||||
}
|
||||
private void tryUpdateValue(T currentPrev) {
|
||||
synchronized (this) {
|
||||
if ((initialized && Objects.equals(prev, currentPrev))
|
||||
|| isComputing(currentPrev)) {
|
||||
return;
|
||||
}
|
||||
U newValue = mapper.apply(arg);
|
||||
synchronized (this) {
|
||||
prev = arg;
|
||||
value = newValue;
|
||||
initialized = true;
|
||||
computing = true;
|
||||
computingPrev = currentPrev;
|
||||
}
|
||||
|
||||
CompletableFuture<U> task;
|
||||
try {
|
||||
task = requireNonNull(mapper.apply(currentPrev));
|
||||
} catch (Throwable e) {
|
||||
valueUpdateFailed(currentPrev);
|
||||
throw e;
|
||||
}
|
||||
|
||||
task.handle((result, e) -> {
|
||||
if (e == null) {
|
||||
valueUpdate(currentPrev, result);
|
||||
Platform.runLater(this::invalidate);
|
||||
} else {
|
||||
handleUncaughtException(e);
|
||||
valueUpdateFailed(currentPrev);
|
||||
}
|
||||
Platform.runLater(this::invalidate);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// called on FX thread, this method is serial
|
||||
@Override
|
||||
protected U computeValue() {
|
||||
T currentPrev = predecessor.getValue();
|
||||
U value;
|
||||
boolean updateNeeded = false;
|
||||
private void valueUpdate(T currentPrev, U computed) {
|
||||
synchronized (this) {
|
||||
value = this.value;
|
||||
if (!initialized || !Objects.equals(currentPrev, prev)) {
|
||||
updateNeeded = true;
|
||||
if (isComputing(currentPrev)) {
|
||||
computing = false;
|
||||
computingPrev = null;
|
||||
prev = currentPrev;
|
||||
value = computed;
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
if (updateNeeded) {
|
||||
dispatcher.accept(currentPrev);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void valueUpdateFailed(T currentPrev) {
|
||||
synchronized (this) {
|
||||
if (isComputing(currentPrev)) {
|
||||
computing = false;
|
||||
computingPrev = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isComputing(T prev) {
|
||||
return computing && Objects.equals(prev, computingPrev);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected U computeValue() {
|
||||
tryUpdateValue(predecessor.getValue());
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ public final class ExtendedProperties {
|
||||
|
||||
private static <T> ObjectProperty<T> createPropertyForSelectionModel(Object bean, Property<? extends SelectionModel<T>> modelProperty) {
|
||||
return new ReadWriteComposedProperty<>(bean, "extra.selectedItem",
|
||||
MultiStepBinding.of(modelProperty)
|
||||
BindingMapping.of(modelProperty)
|
||||
.flatMap(SelectionModel::selectedItemProperty),
|
||||
obj -> modelProperty.getValue().select(obj));
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public final class ExtendedProperties {
|
||||
};
|
||||
|
||||
ReadWriteComposedProperty<T> property = new ReadWriteComposedProperty<>(toggleGroup, "extra.selectedItem",
|
||||
MultiStepBinding.of(selectedTogglePropertyFor(toggleGroup))
|
||||
BindingMapping.of(selectedTogglePropertyFor(toggleGroup))
|
||||
.map(mapper),
|
||||
itemSelector);
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.javafx;
|
||||
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleBooleanProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateBooleanProperty extends SimpleBooleanProperty {
|
||||
|
||||
@Override
|
||||
public void set(boolean newValue) {
|
||||
super.get();
|
||||
super.set(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ObservableValue<? extends Boolean> newObservable) {
|
||||
super.get();
|
||||
super.bind(newObservable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
super.get();
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
public ImmediateBooleanProperty(Object bean, String name, boolean initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<Boolean> changeListener = (a, b, newValue) -> {
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.javafx;
|
||||
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleIntegerProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateIntegerProperty extends SimpleIntegerProperty {
|
||||
|
||||
@Override
|
||||
public void set(int newValue) {
|
||||
super.get();
|
||||
super.set(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ObservableValue<? extends Number> newObservable) {
|
||||
super.get();
|
||||
super.bind(newObservable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
super.get();
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
public ImmediateIntegerProperty(Object bean, String name, int initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<Number> changeListener = (a, b, newValue) -> {
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.javafx;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleObjectProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateObjectProperty<T> extends SimpleObjectProperty<T> {
|
||||
|
||||
@Override
|
||||
public void set(T newValue) {
|
||||
super.get();
|
||||
super.set(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ObservableValue<? extends T> newObservable) {
|
||||
super.get();
|
||||
super.bind(newObservable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
super.get();
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
public ImmediateObjectProperty(Object bean, String name, T initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<T> changeListener = (a, b, newValue) -> {
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.javafx;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleStringProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateStringProperty extends SimpleStringProperty {
|
||||
|
||||
@Override
|
||||
public void set(String newValue) {
|
||||
super.get();
|
||||
super.set(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ObservableValue<? extends String> newObservable) {
|
||||
super.get();
|
||||
super.bind(newObservable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
super.get();
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
private Consumer<String> consumer = null;
|
||||
private ChangeListener<String> listener = null;
|
||||
|
||||
public void setChangedListener(Consumer<String> consumer) {
|
||||
this.consumer = Objects.requireNonNull(consumer);
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
public void setChangedListener(ChangeListener<String> listener) {
|
||||
this.consumer = null;
|
||||
this.listener = Objects.requireNonNull(listener);
|
||||
}
|
||||
|
||||
public ImmediateStringProperty(Object bean, String name, String initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<String> changeListener = (a, b, newValue) -> {
|
||||
if (consumer != null)
|
||||
consumer.accept(newValue);
|
||||
if (listener != null)
|
||||
listener.changed(a, b, newValue);
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user