Merge branch 'javafx' of github.com:huanghongxun/HMCL into javafx

This commit is contained in:
yushijinhun
2019-02-23 23:52:26 +08:00
44 changed files with 351 additions and 414 deletions

View File

@@ -122,14 +122,14 @@ public final class LauncherHelper {
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)) TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
.then(variables -> { .then(() -> {
if (setting.isNotCheckGame()) if (setting.isNotCheckGame())
return null; return null;
else else
return dependencyManager.checkGameCompletionAsync(version); return dependencyManager.checkGameCompletionAsync(version);
}) })
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))) .then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
.then(var -> { .then(() -> {
try { try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion)); ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
if ("Curse".equals(configuration.getType())) 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(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
.then(Task.of(i18n("account.methods"), variables -> { .thenTaskResult(() -> Task.ofResult(i18n("account.methods"), () -> {
try { try {
variables.set("account", account.logIn()); return account.logIn();
} catch (CredentialExpiredException e) { } catch (CredentialExpiredException e) {
LOG.info("Credential has expired: " + e); LOG.info("Credential has expired: " + e);
variables.set("account", DialogController.logIn(account)); return DialogController.logIn(account);
} catch (AuthenticationException e) { } catch (AuthenticationException e) {
LOG.warning("Authentication failed, try playing offline: " + e); LOG.warning("Authentication failed, try playing offline: " + e);
variables.set("account", return account.playOffline().orElseThrow(() -> e);
account.playOffline().orElseThrow(() -> e));
} }
})) }))
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING))) .thenResult(Schedulers.javafx(), authInfo -> {
.then(Task.of(variables -> { emitStatus(LoadingState.LAUNCHING);
variables.set("launcher", new HMCLGameLauncher( return authInfo;
repository, })
selectedVersion, .thenResult(authInfo -> new HMCLGameLauncher(
variables.get("account"), repository,
setting.toLaunchOptions(profile.getGameDir()), selectedVersion,
launcherVisibility == LauncherVisibility.CLOSE authInfo,
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched. setting.toLaunchOptions(profile.getGameDir()),
: new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent()) 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())
.then(variables -> { ))
DefaultLauncher launcher = variables.get("launcher"); .thenTaskResult(launcher -> { // launcher is prev task's result
if (scriptFile == null) { if (scriptFile == null) {
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch")); return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
} else { } else {
return new LaunchTask<>(() -> { return new LaunchTask<ManagedProcess>(() -> {
launcher.makeLaunchScript(scriptFile); launcher.makeLaunchScript(scriptFile);
return null; return null;
}).setName(i18n("version.launch_script")); }).setName(i18n("version.launch_script"));
} }
}) })
.then(Task.of(variables -> { .thenVoid(process -> { // process is LaunchTask's result
if (scriptFile == null) { if (scriptFile == null) {
ManagedProcess process = variables.get(LaunchTask.LAUNCH_ID);
PROCESSES.add(process); PROCESSES.add(process);
if (launcherVisibility == LauncherVisibility.CLOSE) if (launcherVisibility == LauncherVisibility.CLOSE)
Launcher.stopApplication(); Launcher.stopApplication();
@@ -187,13 +185,13 @@ public final class LauncherHelper {
process.stop(); process.stop();
it.fireEvent(new DialogCloseEvent()); it.fireEvent(new DialogCloseEvent());
}); });
} else } else {
Platform.runLater(() -> { Platform.runLater(() -> {
launchingStepsPane.fireEvent(new DialogCloseEvent()); launchingStepsPane.fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath())); Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
}); });
}
})) })
.executor(); .executor();
launchingStepsPane.setExecutor(executor, false); launchingStepsPane.setExecutor(executor, false);
@@ -429,13 +427,6 @@ public final class LauncherHelper {
public void execute() throws Exception { public void execute() throws Exception {
setResult(supplier.get()); setResult(supplier.get());
} }
@Override
public String getId() {
return LAUNCH_ID;
}
static final String LAUNCH_ID = "launch";
} }
/** /**

View File

@@ -110,13 +110,13 @@ public final class ModpackHelper {
if (modpack.getManifest() instanceof CurseManifest) if (modpack.getManifest() instanceof CurseManifest)
return new CurseInstallTask(profile.getDependency(), zipFile, modpack, ((CurseManifest) modpack.getManifest()), name) return new CurseInstallTask(profile.getDependency(), zipFile, modpack, ((CurseManifest) modpack.getManifest()), name)
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure); .finalized(Schedulers.defaultScheduler(), success, failure);
else if (modpack.getManifest() instanceof HMCLModpackManifest) else if (modpack.getManifest() instanceof HMCLModpackManifest)
return new HMCLModpackInstallTask(profile, zipFile, modpack, name) return new HMCLModpackInstallTask(profile, zipFile, modpack, name)
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure); .finalized(Schedulers.defaultScheduler(), success, failure);
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration) else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name) return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
.finalized(Schedulers.defaultScheduler(), ExceptionalConsumer.fromRunnable(success), failure) .finalized(Schedulers.defaultScheduler(), success, failure)
.then(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)); .then(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
else throw new IllegalStateException("Unrecognized modpack: " + modpack); else throw new IllegalStateException("Unrecognized modpack: " + modpack);
} }

View File

@@ -121,13 +121,12 @@ public final class LeftPaneController extends AdvancedListBox {
if (modpackFile.exists()) { if (modpackFile.exists()) {
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath())) Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
.thenResult(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding)) .thenResult(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
.thenResult(modpack -> { .thenVoid(modpack -> {
AtomicReference<Region> region = new AtomicReference<>(); AtomicReference<Region> region = new AtomicReference<>();
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack) TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), this::checkAccount)).executor(); .with(Task.of(Schedulers.javafx(), this::checkAccount)).executor();
region.set(Controllers.taskDialog(executor, i18n("modpack.installing"))); region.set(Controllers.taskDialog(executor, i18n("modpack.installing")));
executor.start(); executor.start();
return null;
}).start(); }).start();
} }
} }

View File

@@ -62,7 +62,7 @@ public class AccountLoginPane extends StackPane {
progressBar.setVisible(true); progressBar.setVisible(true);
lblCreationWarning.setText(""); lblCreationWarning.setText("");
Task.ofResult(() -> oldAccount.logInWithPassword(password)) Task.ofResult(() -> oldAccount.logInWithPassword(password))
.finalizedResult(Schedulers.javafx(), authInfo -> { .finalized(Schedulers.javafx(), authInfo -> {
success.accept(authInfo); success.accept(authInfo);
fireEvent(new DialogCloseEvent()); fireEvent(new DialogCloseEvent());
progressBar.setVisible(false); progressBar.setVisible(false);

View File

@@ -196,7 +196,7 @@ public class AddAccountPane extends StackPane {
Object additionalData = getAuthAdditionalData(); Object additionalData = getAuthAdditionalData();
Task.ofResult(() -> factory.create(new Selector(), username, password, additionalData)) Task.ofResult(() -> factory.create(new Selector(), username, password, additionalData))
.finalizedResult(Schedulers.javafx(), account -> { .finalized(Schedulers.javafx(), account -> {
int oldIndex = Accounts.getAccounts().indexOf(account); int oldIndex = Accounts.getAccounts().indexOf(account);
if (oldIndex == -1) { if (oldIndex == -1) {
Accounts.getAccounts().add(account); Accounts.getAccounts().add(account);

View File

@@ -106,7 +106,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
Task.of(() -> { Task.of(() -> {
serverBeingAdded = AuthlibInjectorServer.locateServer(url); serverBeingAdded = AuthlibInjectorServer.locateServer(url);
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded, exception) -> { }).finalized(Schedulers.javafx(), (isDependentsSucceeded, exception) -> {
addServerPane.setDisable(false); addServerPane.setDisable(false);
nextPane.hideSpinner(); nextPane.hideSpinner();

View File

@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.DownloadException; import org.jackhuang.hmcl.task.DownloadException;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardController;
@@ -89,16 +90,16 @@ public final class InstallerWizardProvider implements WizardProvider {
settings.put("success_message", i18n("install.success")); settings.put("success_message", i18n("install.success"));
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next)); 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")) if (settings.containsKey("forge"))
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge"))); ret = ret.thenTaskResult(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
if (settings.containsKey("liteloader")) if (settings.containsKey("liteloader"))
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader"))); ret = ret.thenTaskResult(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
if (settings.containsKey("optifine")) if (settings.containsKey("optifine"))
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine"))); ret = ret.thenTaskResult(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
return ret.then(profile.getRepository().refreshVersionsAsync()); return ret.then(profile.getRepository().refreshVersionsAsync());
} }

View File

@@ -111,7 +111,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
spinnerPane.showSpinner(); spinnerPane.showSpinner();
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath())) Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
.thenResult(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding)) .thenResult(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
.finalizedResult(Schedulers.javafx(), manifest -> { .finalized(Schedulers.javafx(), manifest -> {
spinnerPane.hideSpinner(); spinnerPane.hideSpinner();
controller.getSettings().put(MODPACK_MANIFEST, manifest); controller.getSettings().put(MODPACK_MANIFEST, manifest);
lblName.setText(manifest.getName()); lblName.setText(manifest.getName());

View File

@@ -59,7 +59,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
if (settings.containsKey("optifine")) if (settings.containsKey("optifine"))
builder.version((RemoteVersion) settings.get("optifine")); builder.version((RemoteVersion) settings.get("optifine"));
return builder.buildAsync().finalized((a, b, c) -> profile.getRepository().refreshVersions()) return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions())
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name))); .then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
} }

View File

@@ -128,7 +128,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
@Override @Override
public void refresh() { public void refresh() {
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer()); transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
executor = versionList.refreshAsync(gameVersion, downloadProvider).finalized((variables, isDependentsSucceeded, exception) -> { executor = versionList.refreshAsync(gameVersion, downloadProvider).finalized((isDependentsSucceeded, exception) -> {
if (isDependentsSucceeded) { if (isDependentsSucceeded) {
List<VersionsPageItem> items = loadVersions(); List<VersionsPageItem> items = loadVersions();

View File

@@ -54,7 +54,7 @@ public class InstallerListPage extends ListPage<InstallerItem> {
LinkedList<Library> newList = new LinkedList<>(version.getLibraries()); LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
newList.remove(library); newList.remove(library);
new MaintainTask(version.setLibraries(newList)) 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(profile.getRepository().refreshVersionsAsync())
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))) .with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
.start(); .start();

View File

@@ -36,7 +36,6 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
@@ -86,13 +85,13 @@ public final class ModListPage extends Control {
public void loadMods(ModManager modManager) { public void loadMods(ModManager modManager) {
this.modManager = modManager; this.modManager = modManager;
Task.ofResult("list", variables -> { Task.ofResult(() -> {
synchronized (ModListPage.this) { synchronized (ModListPage.this) {
JFXUtilities.runInFX(() -> loadingProperty().set(true)); JFXUtilities.runInFX(() -> loadingProperty().set(true));
modManager.refreshMods(); modManager.refreshMods();
return new LinkedList<>(modManager.getMods()); return new LinkedList<>(modManager.getMods());
} }
}).finalizedResult(Schedulers.javafx(), (list, isDependentsSucceeded, exception) -> { }).finalized(Schedulers.javafx(), (list, isDependentsSucceeded, exception) -> {
loadingProperty().set(false); loadingProperty().set(false);
if (isDependentsSucceeded) if (isDependentsSucceeded)
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
@@ -112,7 +111,7 @@ public final class ModListPage extends Control {
List<String> succeeded = new LinkedList<>(); List<String> succeeded = new LinkedList<>();
List<String> failed = new LinkedList<>(); List<String> failed = new LinkedList<>();
if (res == null) return; if (res == null) return;
Task.of(variables -> { Task.of(() -> {
for (File file : res) { for (File file : res) {
try { try {
modManager.addMod(file); 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. // 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<>(); List<String> prompt = new LinkedList<>();
if (!succeeded.isEmpty()) if (!succeeded.isEmpty())
prompt.add(i18n("mods.add.success", String.join(", ", succeeded))); prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));

View File

@@ -57,6 +57,7 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -117,14 +118,13 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
FXUtils.smoothScrolling(scroll); FXUtils.smoothScrolling(scroll);
Task.ofResult(JavaVersion::getJavas).thenResult(Schedulers.javafx(), list -> { Task.ofResult(JavaVersion::getJavas).thenVoid(Schedulers.javafx(), list -> {
javaItem.loadChildren(list.stream() javaItem.loadChildren(list.stream()
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit", .map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit",
javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion)) javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion))
.collect(Collectors.toList())); .collect(Collectors.toList()));
javaItemsLoaded = true; javaItemsLoaded = true;
initializeSelectedJava(); initializeSelectedJava();
return null;
}).start(); }).start();
javaItem.setSelectedData(null); javaItem.setSelectedData(null);
@@ -273,10 +273,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
VersionSetting versionSetting = lastVersionSetting; VersionSetting versionSetting = lastVersionSetting;
if (versionSetting == null) if (versionSetting == null)
return; return;
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion())) Task.ofResult(versionSetting::getJavaVersion)
.subscribe(Task.of(Schedulers.javafx(), .thenVoid(Schedulers.javafx(), javaVersion -> javaItem.setSubtitle(Optional.ofNullable(javaVersion)
variables -> javaItem.setSubtitle(variables.<JavaVersion>getOptional("java") .map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))); .start();
} }
@FXML @FXML

View File

@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
@@ -52,7 +51,7 @@ public class WorldListPage extends ListPage<WorldListItem> {
setLoading(true); setLoading(true);
Task.ofResult(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList())) Task.ofResult(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
.finalizedResult(Schedulers.javafx(), (result, isDependentsSucceeded, exception) -> { .finalized(Schedulers.javafx(), (result, isDependentsSucceeded, exception) -> {
setLoading(false); setLoading(false);
if (isDependentsSucceeded) if (isDependentsSucceeded)
itemsProperty().setAll(result.stream().map(WorldListItem::new).collect(Collectors.toList())); itemsProperty().setAll(result.stream().map(WorldListItem::new).collect(Collectors.toList()));
@@ -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 // Only accept one world file because user is required to confirm the new world name
// Or too many input dialogs are popped. // Or too many input dialogs are popped.
Task.ofResult(() -> new World(zipFile.toPath())) Task.ofResult(() -> new World(zipFile.toPath()))
.finalizedResult(Schedulers.javafx(), world -> { .finalized(Schedulers.javafx(), world -> {
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> { Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.of(() -> world.install(savesDir, name)) Task.of(() -> world.install(savesDir, name))
.finalized(Schedulers.javafx(), var -> { .finalized(Schedulers.javafx(), () -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name)))); itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
resolve.run(); resolve.run();
}, e -> { }, e -> {

View File

@@ -28,7 +28,7 @@ import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask; import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task; 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.function.ExceptionalFunction;
/** /**
@@ -71,7 +71,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
@Override @Override
public Task checkGameCompletionAsync(Version version) { public Task checkGameCompletionAsync(Version version) {
return new ParallelTask( return new ParallelTask(
Task.ofThen(var -> { Task.ofThen(() -> {
if (!repository.getVersionJar(version).exists()) if (!repository.getVersionJar(version).exists())
return new GameDownloadTask(this, null, version); return new GameDownloadTask(this, null, version);
else else
@@ -88,36 +88,32 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
} }
@Override @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); VersionList<?> versionList = getVersionList(libraryId);
return versionList.loadAsync(gameVersion, getDownloadProvider()) return versionList.loadAsync(gameVersion, getDownloadProvider())
.then(variables -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion) .thenTaskResult(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
.orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion)))); .orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion))));
} }
@Override @Override
public Task installLibraryAsync(Version version, RemoteVersion libraryVersion) { public TaskResult<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) {
TaskResult<Version> task;
if (libraryVersion instanceof ForgeRemoteVersion) if (libraryVersion instanceof ForgeRemoteVersion)
return new ForgeInstallTask(this, version, (ForgeRemoteVersion) libraryVersion) task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion);
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
.then(variables -> new MaintainTask(variables.get("version")))
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
else if (libraryVersion instanceof LiteLoaderRemoteVersion) else if (libraryVersion instanceof LiteLoaderRemoteVersion)
return new LiteLoaderInstallTask(this, version, (LiteLoaderRemoteVersion) libraryVersion) task = new LiteLoaderInstallTask(this, oldVersion, (LiteLoaderRemoteVersion) libraryVersion);
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
.then(variables -> new MaintainTask(variables.get("version")))
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
else if (libraryVersion instanceof OptiFineRemoteVersion) else if (libraryVersion instanceof OptiFineRemoteVersion)
return new OptiFineInstallTask(this, version, (OptiFineRemoteVersion) libraryVersion) task = new OptiFineInstallTask(this, oldVersion, (OptiFineRemoteVersion) libraryVersion);
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
.then(variables -> new MaintainTask(variables.get("version")))
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
else else
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized."); throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
return task
.thenTaskResult(LibrariesUniqueTask::new)
.thenTaskResult(MaintainTask::new)
.thenTaskResult(newVersion -> new VersionJsonSaveTask(repository, newVersion));
} }
public ExceptionalFunction<AutoTypingMap<String>, Task, ?> installLibraryAsync(RemoteVersion libraryVersion) { public ExceptionalFunction<Version, TaskResult<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
return var -> installLibraryAsync(var.get("version"), libraryVersion); return version -> installLibraryAsync(version, libraryVersion);
} }
} }

View File

@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.download.game.*;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask; import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.AutoTypingMap; import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -49,34 +50,35 @@ public class DefaultGameBuilder extends GameBuilder {
@Override @Override
public Task buildAsync() { public Task buildAsync() {
return new VersionJsonDownloadTask(gameVersion, dependencyManager).then(variables -> { return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenTaskResult(rawJson -> {
Version version = JsonUtils.GSON.fromJson(variables.<String>get(VersionJsonDownloadTask.ID), Version.class); Version original = JsonUtils.GSON.fromJson(rawJson, Version.class);
version = version.setId(name).setJar(null); Version version = original.setId(name).setJar(null);
variables.set("version", version); Task vanillaTask = downloadGameAsync(gameVersion, version).then(new ParallelTask(
Task result = downloadGameAsync(gameVersion, version).then(new ParallelTask(
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY), 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. 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. ).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version))); // using [with] because download failure here are tolerant.
TaskResult<Version> libraryTask = vanillaTask.thenResult(() -> version);
if (toolVersions.containsKey("forge")) if (toolVersions.containsKey("forge"))
result = result.then(libraryTaskHelper(gameVersion, "forge")); libraryTask = libraryTask.thenTaskResult(libraryTaskHelper(gameVersion, "forge"));
if (toolVersions.containsKey("liteloader")) if (toolVersions.containsKey("liteloader"))
result = result.then(libraryTaskHelper(gameVersion, "liteloader")); libraryTask = libraryTask.thenTaskResult(libraryTaskHelper(gameVersion, "liteloader"));
if (toolVersions.containsKey("optifine")) if (toolVersions.containsKey("optifine"))
result = result.then(libraryTaskHelper(gameVersion, "optifine")); libraryTask = libraryTask.thenTaskResult(libraryTaskHelper(gameVersion, "optifine"));
for (RemoteVersion remoteVersion : remoteVersions) for (RemoteVersion remoteVersion : remoteVersions)
result = result.then(dependencyManager.installLibraryAsync(remoteVersion)); libraryTask = libraryTask.thenTaskResult(dependencyManager.installLibraryAsync(remoteVersion));
return result; return libraryTask;
}).finalized((variables, isDependentsSucceeded, exception) -> { }).finalized((isDependentsSucceeded, exception) -> {
if (!isDependentsSucceeded) if (!isDependentsSucceeded)
dependencyManager.getGameRepository().getVersionRoot(name).delete(); dependencyManager.getGameRepository().getVersionRoot(name).delete();
}); });
} }
private ExceptionalFunction<AutoTypingMap<String>, Task, ?> libraryTaskHelper(String gameVersion, String libraryId) { private ExceptionalFunction<Version, TaskResult<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId) {
return variables -> dependencyManager.installLibraryAsync(gameVersion, variables.get("version"), libraryId, toolVersions.get(libraryId)); return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, toolVersions.get(libraryId));
} }
protected Task downloadGameAsync(String gameVersion, Version version) { protected Task downloadGameAsync(String gameVersion, Version version) {

View File

@@ -23,15 +23,9 @@ import org.jackhuang.hmcl.task.TaskResult;
public class MaintainTask extends TaskResult<Version> { public class MaintainTask extends TaskResult<Version> {
private final Version version; private final Version version;
private final String id;
public MaintainTask(Version version) { public MaintainTask(Version version) {
this(version, ID);
}
public MaintainTask(Version version, String id) {
this.version = version; this.version = version;
this.id = id;
} }
@Override @Override
@@ -76,11 +70,4 @@ public class MaintainTask extends TaskResult<Version> {
return builder.build(); return builder.build();
} }
@Override
public String getId() {
return id;
}
public static final String ID = "version";
} }

View File

@@ -74,7 +74,7 @@ public abstract class VersionList<T extends RemoteVersion> {
} }
public Task loadAsync(DownloadProvider downloadProvider) { public Task loadAsync(DownloadProvider downloadProvider) {
return Task.ofThen(variables -> { return Task.ofThen(() -> {
lock.readLock().lock(); lock.readLock().lock();
boolean loaded; boolean loaded;
@@ -88,7 +88,7 @@ public abstract class VersionList<T extends RemoteVersion> {
} }
public Task loadAsync(String gameVersion, DownloadProvider downloadProvider) { public Task loadAsync(String gameVersion, DownloadProvider downloadProvider) {
return Task.ofThen(variables -> { return Task.ofThen(() -> {
lock.readLock().lock(); lock.readLock().lock();
boolean loaded; boolean loaded;

View File

@@ -82,11 +82,6 @@ public final class ForgeInstallTask extends TaskResult<Version> {
return Collections.singleton(dependency); return Collections.singleton(dependency);
} }
@Override
public String getId() {
return "version";
}
@Override @Override
public boolean isRelyingOnDependencies() { public boolean isRelyingOnDependencies() {
return false; return false;

View File

@@ -89,11 +89,6 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
return dependencies; return dependencies;
} }
@Override
public String getId() {
return "version";
}
@Override @Override
public boolean doPreExecute() { public boolean doPreExecute() {
return true; return true;

View File

@@ -49,11 +49,6 @@ public class ForgeOldInstallTask extends TaskResult<Version> {
return dependencies; return dependencies;
} }
@Override
public String getId() {
return "version";
}
@Override @Override
public boolean doPreExecute() { public boolean doPreExecute() {
return true; return true;

View File

@@ -33,15 +33,9 @@ import java.util.stream.Collectors;
public class LibrariesUniqueTask extends TaskResult<Version> { public class LibrariesUniqueTask extends TaskResult<Version> {
private final Version version; private final Version version;
private final String id;
public LibrariesUniqueTask(Version version) { public LibrariesUniqueTask(Version version) {
this(version, "version");
}
public LibrariesUniqueTask(Version version, String id) {
this.version = version; this.version = version;
this.id = id;
} }
@Override @Override
@@ -99,9 +93,4 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
setResult(version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList()))); setResult(version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList())));
} }
@Override
public String getId() {
return id;
}
} }

View File

@@ -225,16 +225,16 @@ public class LibraryDownloadTask extends Task {
int x = decompressed.length; 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; 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); byte[] checksums = Arrays.copyOfRange(decompressed, decompressed.length - len - 8, decompressed.length - 8);
OutputStream out = new FileOutputStream(temp); try (OutputStream out = Files.newOutputStream(temp)) {
out.write(decompressed, 0, decompressed.length - len - 8); out.write(decompressed, 0, decompressed.length - len - 8);
out.close(); }
try (FileOutputStream jarBytes = new FileOutputStream(dest); JarOutputStream jos = new JarOutputStream(jarBytes)) { 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"); JarEntry checksumsFile = new JarEntry("checksums.sha1");
checksumsFile.setTime(0L); checksumsFile.setTime(0L);
@@ -243,6 +243,6 @@ public class LibraryDownloadTask extends Task {
jos.closeEntry(); jos.closeEntry();
} }
temp.delete(); Files.delete(temp);
} }
} }

View File

@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask; import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.util.Collection; import java.util.Collection;
@@ -32,7 +33,7 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class VersionJsonDownloadTask extends Task { public final class VersionJsonDownloadTask extends TaskResult<String> {
private final String gameVersion; private final String gameVersion;
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final List<Task> dependents = new LinkedList<>(); private final List<Task> dependents = new LinkedList<>();
@@ -65,8 +66,6 @@ public final class VersionJsonDownloadTask extends Task {
RemoteVersion remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst() RemoteVersion remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find specific version " + gameVersion + " in remote repository")); .orElseThrow(() -> new IllegalStateException("Cannot find specific version " + gameVersion + " in remote repository"));
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl()); 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";
} }

View File

@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version; 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.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
@@ -30,7 +30,7 @@ import java.io.File;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class VersionJsonSaveTask extends Task { public final class VersionJsonSaveTask extends TaskResult<Version> {
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
private final Version version; private final Version version;
@@ -46,6 +46,7 @@ public final class VersionJsonSaveTask extends Task {
this.version = version; this.version = version;
setSignificance(TaskSignificance.MODERATE); setSignificance(TaskSignificance.MODERATE);
setResult(version);
} }
@Override @Override

View File

@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.download.liteloader; package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo; import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo; import org.jackhuang.hmcl.game.LibraryDownloadInfo;
@@ -61,11 +60,6 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
return dependencies; return dependencies;
} }
@Override
public String getId() {
return "version";
}
@Override @Override
public void execute() { public void execute() {
Library library = new Library( Library library = new Library(

View File

@@ -59,11 +59,6 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
return dependencies; return dependencies;
} }
@Override
public String getId() {
return "version";
}
@Override @Override
public boolean isRelyingOnDependencies() { public boolean isRelyingOnDependencies() {
return false; return false;

View File

@@ -168,8 +168,8 @@ public class DefaultGameRepository implements GameRepository {
versions.remove(id); versions.remove(id);
if (FileUtils.isMovingToTrashSupported()) { if (FileUtils.isMovingToTrashSupported() && FileUtils.moveToTrash(removedFile)) {
return FileUtils.moveToTrash(removedFile); return true;
} }
// remove json files first to ensure HMCL will not recognize this folder as a valid version. // remove json files first to ensure HMCL will not recognize this folder as a valid version.

View File

@@ -23,7 +23,7 @@ import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils; 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.File;
import java.io.IOException; import java.io.IOException;
@@ -119,7 +119,7 @@ public final class ForgeModMetadata {
Path mcmod = fs.getPath("mcmod.info"); Path mcmod = fs.getPath("mcmod.info");
if (Files.notExists(mcmod)) if (Files.notExists(mcmod))
throw new IOException("File " + modFile + " is not a Forge mod."); 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>>() { new TypeToken<List<ForgeModMetadata>>() {
}.getType()); }.getType());
if (modList == null || modList.isEmpty()) if (modList == null || modList.isEmpty())

View File

@@ -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).thenTaskResult(maintainedVersion -> new VersionJsonSaveTask(repository, maintainedVersion)));
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name))); dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
} }

View File

@@ -18,57 +18,53 @@
package org.jackhuang.hmcl.task; package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.AutoTypingMap; 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.Collection;
import java.util.Collections; 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. * A task that combines two tasks and make sure [pred] runs before succ.
* *
* @author huangyuhui * @author huangyuhui
*/ */
final class CoupleTask<P extends Task> extends Task { final class CoupleTask extends Task {
private final boolean relyingOnDependents; private final boolean relyingOnDependents;
private final Collection<Task> dependents; private final Task pred;
private final List<Task> dependencies = new LinkedList<>(); private Task succ;
private final ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ; private final ExceptionalSupplier<Task, ?> supplier;
/** /**
* A task that combines two tasks and make sure pred runs before succ. * A task that combines two tasks and make sure pred runs before succ.
* *
* @param pred the task that runs before succ. * @param pred the task that runs before supplier.
* @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 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. * @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) { CoupleTask(Task pred, ExceptionalSupplier<Task, ?> supplier, boolean relyingOnDependents) {
this.dependents = pred == null ? Collections.emptySet() : Collections.singleton(pred); this.pred = pred;
this.succ = succ; this.supplier = supplier;
this.relyingOnDependents = relyingOnDependents; this.relyingOnDependents = relyingOnDependents;
setSignificance(TaskSignificance.MODERATE); setSignificance(TaskSignificance.MODERATE);
setName(succ.toString()); setName(supplier.toString());
} }
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
setName(succ.toString()); setName(supplier.toString());
Task task = succ.apply(getVariables()); succ = supplier.get();
if (task != null)
dependencies.add(task);
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task> getDependents() {
return dependents; return pred == null ? Collections.emptySet() : Collections.singleton(pred);
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task> getDependencies() {
return dependencies; return succ == null ? Collections.emptySet() : Collections.singleton(succ);
} }
@Override @Override

View File

@@ -33,6 +33,7 @@ import java.io.RandomAccessFile;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Optional; import java.util.Optional;
@@ -212,7 +213,7 @@ public class FileDownloadTask extends Task {
break; break;
} }
File temp = null; Path temp = null;
try { try {
updateProgress(0); updateProgress(0);
@@ -242,15 +243,15 @@ public class FileDownloadTask extends Task {
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
throw new IOException("Could not make directory " + file.getAbsoluteFile().getParent()); throw new IOException("Could not make directory " + file.getAbsoluteFile().getParent());
temp = FileUtils.createTempFile(); temp = Files.createTempFile(null, null);
rFile = new RandomAccessFile(temp, "rw"); rFile = new RandomAccessFile(temp.toFile(), "rw");
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest(); MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
stream = con.getInputStream(); stream = con.getInputStream();
int lastDownloaded = 0, downloaded = 0; int lastDownloaded = 0, downloaded = 0;
long lastTime = System.currentTimeMillis(); long lastTime = System.currentTimeMillis();
byte buffer[] = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
while (true) { while (true) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@@ -283,16 +284,15 @@ public class FileDownloadTask extends Task {
// Restore temp file to original name. // Restore temp file to original name.
if (Thread.interrupted()) { if (Thread.interrupted()) {
temp.delete(); temp.toFile().delete();
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
break; break;
} else { } else {
if (file.exists() && !file.delete()) Files.deleteIfExists(file.toPath());
throw new IOException("Unable to delete existent file " + file);
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
throw new IOException("Unable to make parent directory " + file); throw new IOException("Unable to make parent directory " + file);
try { try {
FileUtils.moveFile(temp, file); FileUtils.moveFile(temp.toFile(), file);
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Unable to move temp file from " + temp + " to " + file, e); throw new IOException("Unable to move temp file from " + temp + " to " + file, e);
} }
@@ -321,7 +321,7 @@ public class FileDownloadTask extends Task {
return; return;
} catch (IOException | IllegalStateException e) { } catch (IOException | IllegalStateException e) {
if (temp != null) if (temp != null)
temp.delete(); temp.toFile().delete();
exception = e; exception = e;
} finally { } finally {
closeFiles(); closeFiles();

View File

@@ -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, Exception exception) throws Exception;
}

View File

@@ -37,13 +37,12 @@ final class FinalizedTask extends Task {
* @param pred the task that 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. * @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) { public FinalizedTask(Task pred, Scheduler scheduler, FinalizedCallback callback) {
this.pred = pred; this.pred = pred;
this.scheduler = scheduler; this.scheduler = scheduler;
this.callback = callback; this.callback = callback;
setSignificance(TaskSignificance.MODERATE); setSignificance(TaskSignificance.MODERATE);
setName(name);
} }
@Override @Override
@@ -53,7 +52,7 @@ final class FinalizedTask extends Task {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
callback.execute(getVariables(), isDependentsSucceeded(), pred.getLastException()); callback.execute(isDependentsSucceeded(), pred.getLastException());
if (!isDependentsSucceeded()) if (!isDependentsSucceeded())
throw new SilentException(); throw new SilentException();

View File

@@ -72,11 +72,6 @@ public final class GetTask extends TaskResult<String> {
return Schedulers.io(); return Schedulers.io();
} }
@Override
public String getId() {
return id;
}
public GetTask setCacheRepository(CacheRepository repository) { public GetTask setCacheRepository(CacheRepository repository) {
this.repository = repository; this.repository = repository;
return this; return this;

View File

@@ -17,8 +17,7 @@
*/ */
package org.jackhuang.hmcl.task; package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.AutoTypingMap; import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
/** /**
* *
@@ -26,16 +25,16 @@ import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
*/ */
class SimpleTask extends Task { class SimpleTask extends Task {
private final ExceptionalConsumer<AutoTypingMap<String>, ?> consumer; private final ExceptionalRunnable<?> closure;
private final Scheduler scheduler; private final Scheduler scheduler;
public SimpleTask(String name, ExceptionalConsumer<AutoTypingMap<String>, ?> consumer, Scheduler scheduler) { public SimpleTask(String name, ExceptionalRunnable<?> closure, Scheduler scheduler) {
this.consumer = consumer; this.closure = closure;
this.scheduler = scheduler; this.scheduler = scheduler;
if (name == null) { if (name == null) {
setSignificance(TaskSignificance.MINOR); setSignificance(TaskSignificance.MINOR);
setName(consumer.toString()); setName(closure.toString());
} else { } else {
setName(name); setName(name);
} }
@@ -48,6 +47,6 @@ class SimpleTask extends Task {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
consumer.accept(getVariables()); closure.run();
} }
} }

View File

@@ -17,24 +17,30 @@
*/ */
package org.jackhuang.hmcl.task; 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 org.jackhuang.hmcl.util.function.ExceptionalSupplier;
public final class SimpleTaskResult<V> extends TaskResult<V> { import java.util.concurrent.Callable;
private final String id;
private final ExceptionalSupplier<V, ?> supplier;
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 @Override
public void execute() throws Exception { public void execute() throws Exception {
setResult(supplier.get()); setResult(callable.call());
}
@Override
public String getId() {
return id;
} }
} }

View File

@@ -23,13 +23,12 @@ import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper; import javafx.beans.property.ReadOnlyStringWrapper;
import org.jackhuang.hmcl.event.EventManager; import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.InvocationDispatcher; import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.ReflectionHelper; import org.jackhuang.hmcl.util.ReflectionHelper;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer; 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.ExceptionalRunnable;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -134,16 +133,6 @@ public abstract class Task {
return this; return this;
} }
private AutoTypingMap<String> variables = null;
public AutoTypingMap<String> getVariables() {
return variables;
}
void setVariables(AutoTypingMap<String> variables) {
this.variables = variables;
}
public boolean doPreExecute() { public boolean doPreExecute() {
return false; return false;
} }
@@ -280,16 +269,8 @@ public abstract class Task {
new TaskExecutor(then(subscriber)).start(); 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) { public final void subscribe(Scheduler scheduler, ExceptionalRunnable<?> closure) {
subscribe(of(scheduler, ExceptionalConsumer.fromRunnable(closure))); subscribe(of(scheduler, closure));
}
public final void subscribe(ExceptionalConsumer<AutoTypingMap<String>, ?> closure) {
subscribe(of(closure));
} }
public final void subscribe(ExceptionalRunnable<?> closure) { public final void subscribe(ExceptionalRunnable<?> closure) {
@@ -300,16 +281,41 @@ public abstract class Task {
return then(convert(b)); return then(convert(b));
} }
public final Task then(ExceptionalFunction<AutoTypingMap<String>, Task, ?> b) { public final Task then(ExceptionalSupplier<Task, ?> b) {
return new CoupleTask<>(this, b, true); return new CoupleTask(this, b, true);
}
public final <R> TaskResult<R> thenResult(Callable<R> supplier) {
return thenTaskResult(() -> Task.ofResult(supplier));
}
public final <R> TaskResult<R> thenTaskResult(ExceptionalSupplier<TaskResult<R>, ?> taskSupplier) {
return new TaskResult<R>() {
TaskResult<R> then;
@Override
public Collection<? extends Task> getDependents() {
return Collections.singleton(Task.this);
}
@Override
public void execute() throws Exception {
then = taskSupplier.get().storeTo(this::setResult);
}
@Override
public Collection<? extends Task> getDependencies() {
return then == null ? Collections.emptyList() : Collections.singleton(then);
}
};
} }
public final Task with(Task b) { public final Task with(Task b) {
return with(convert(b)); return with(convert(b));
} }
public final <E extends Exception> Task with(ExceptionalFunction<AutoTypingMap<String>, Task, E> b) { public final <E extends Exception> Task with(ExceptionalSupplier<Task, E> b) {
return new CoupleTask<>(this, b, false); return new CoupleTask(this, b, false);
} }
public final Task finalized(FinalizedCallback b) { public final Task finalized(FinalizedCallback b) {
@@ -317,15 +323,16 @@ public abstract class Task {
} }
public final Task finalized(Scheduler scheduler, FinalizedCallback b) { public final Task finalized(Scheduler scheduler, FinalizedCallback b) {
return new FinalizedTask(this, scheduler, b, ReflectionHelper.getCaller().toString()); return new FinalizedTask(this, scheduler, b).setName(getCaller());
} }
public final <T extends Exception, K extends Exception> Task finalized(Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, T> success, ExceptionalConsumer<Exception, K> failure) { // T, K here is necessary, or javac cannot infer type of failure
return finalized(scheduler, (variables, isDependentsSucceeded, exception) -> { public final <T extends Exception, K extends Exception> Task finalized(Scheduler scheduler, ExceptionalRunnable<T> success, ExceptionalConsumer<Exception, K> failure) {
return finalized(scheduler, (isDependentsSucceeded, exception) -> {
if (isDependentsSucceeded) { if (isDependentsSucceeded) {
if (success != null) if (success != null)
try { try {
success.accept(variables); success.run();
} catch (Exception e) { } catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Failed to execute " + success, e); Logging.LOG.log(Level.WARNING, "Failed to execute " + success, e);
if (failure != null) if (failure != null)
@@ -338,58 +345,38 @@ public abstract class Task {
}); });
} }
public static Task of(String name, ExceptionalRunnable<?> runnable) { public static Task of(ExceptionalRunnable<?> closure) {
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) {
return of(Schedulers.defaultScheduler(), closure); return of(Schedulers.defaultScheduler(), closure);
} }
public static Task of(String name, Scheduler scheduler, ExceptionalConsumer<AutoTypingMap<String>, ?> closure) { public static Task of(String name, ExceptionalRunnable<?> closure) {
return new SimpleTask(name, closure, scheduler); return of(name, Schedulers.defaultScheduler(), closure);
}
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(Scheduler scheduler, ExceptionalRunnable<?> 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) { public static Task of(String name, Scheduler scheduler, ExceptionalRunnable<?> closure) {
return new CoupleTask<>(null, b, true); 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) { 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) { public static <V> TaskResult<V> ofResult(String name, Callable<V> callable) {
return new TaskCallable<>(id, callable); return new SimpleTaskResult<>(callable).setName(name);
} }
public static <V> TaskResult<V> ofResult(String id, ExceptionalFunction<AutoTypingMap<String>, V, ?> closure) { private static ExceptionalSupplier<Task, ?> convert(Task t) {
return new TaskCallable<>(id, closure); return new ExceptionalSupplier<Task, Exception>() {
}
private static ExceptionalFunction<AutoTypingMap<String>, Task, ?> convert(Task t) {
return new ExceptionalFunction<AutoTypingMap<String>, Task, Exception>() {
@Override @Override
public Task apply(AutoTypingMap<String> autoTypingMap) { public Task get() {
return t; return t;
} }
@@ -421,4 +408,12 @@ public abstract class Task {
SUCCEEDED, SUCCEEDED,
FAILED FAILED
} }
public interface FinalizedCallback {
void execute(boolean isDependentsSucceeded, Exception exception) throws Exception;
}
static String getCaller() {
return ReflectionHelper.getCaller(packageName -> !"org.jackhuang.hmcl.task".equals(packageName)).toString();
}
} }

View File

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

View File

@@ -38,7 +38,6 @@ public final class TaskExecutor {
private Exception lastException; private Exception lastException;
private final AtomicInteger totTask = new AtomicInteger(0); private final AtomicInteger totTask = new AtomicInteger(0);
private final ConcurrentLinkedQueue<Future<?>> workerQueue = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<Future<?>> workerQueue = new ConcurrentLinkedQueue<>();
private final AutoTypingMap<String> variables = new AutoTypingMap<>(new HashMap<>());
private Scheduler scheduler = Schedulers.newThread(); private Scheduler scheduler = Schedulers.newThread();
public TaskExecutor(Task task) { public TaskExecutor(Task task) {
@@ -147,8 +146,6 @@ public final class TaskExecutor {
boolean flag = false; boolean flag = false;
try { try {
task.setVariables(variables);
if (task.doPreExecute()) { if (task.doPreExecute()) {
try { try {
task.getScheduler().schedule(task::preExecute).get(); task.getScheduler().schedule(task::preExecute).get();
@@ -186,11 +183,6 @@ public final class TaskExecutor {
task.setState(Task.TaskState.EXECUTED); task.setState(Task.TaskState.EXECUTED);
} }
if (task instanceof TaskResult<?>) {
TaskResult<?> taskResult = (TaskResult<?>) task;
variables.set(taskResult.getId(), taskResult.getResult());
}
Collection<? extends Task> dependencies = task.getDependencies(); Collection<? extends Task> dependencies = task.getDependencies();
boolean doDependenciesSucceeded = executeTasks(dependencies); boolean doDependenciesSucceeded = executeTasks(dependencies);
Exception dependenciesException = dependencies.stream().map(Task::getLastException).filter(Objects::nonNull).findAny().orElse(null); Exception dependenciesException = dependencies.stream().map(Task::getLastException).filter(Objects::nonNull).findAny().orElse(null);
@@ -238,8 +230,6 @@ public final class TaskExecutor {
} }
task.onDone().fireEvent(new TaskEvent(this, task, true)); task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListeners.forEach(it -> it.onFailed(task, e)); taskListeners.forEach(it -> it.onFailed(task, e));
} finally {
task.setVariables(null);
} }
task.setState(flag ? Task.TaskState.SUCCEEDED : Task.TaskState.FAILED); task.setState(flag ? Task.TaskState.SUCCEEDED : Task.TaskState.FAILED);
return flag; return flag;
@@ -249,10 +239,6 @@ public final class TaskExecutor {
return totTask.get(); return totTask.get();
} }
public AutoTypingMap<String> getVariables() {
return variables;
}
private class Invoker implements ExceptionalRunnable<Exception> { private class Invoker implements ExceptionalRunnable<Exception> {
private final Task task; private final Task task;

View File

@@ -17,12 +17,12 @@
*/ */
package org.jackhuang.hmcl.task; package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.ReflectionHelper;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer; import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.function.Consumer;
/** /**
* A task that has a result. * A task that has a result.
@@ -32,6 +32,13 @@ import java.util.Collections;
public abstract class TaskResult<V> extends Task { public abstract class TaskResult<V> extends Task {
private V result; private V result;
private Consumer<V> resultConsumer;
@Override
public TaskResult<V> setName(String name) {
super.setName(name);
return this;
}
public V getResult() { public V getResult() {
return result; return result;
@@ -39,41 +46,78 @@ public abstract class TaskResult<V> extends Task {
public void setResult(V result) { public void setResult(V result) {
this.result = result; this.result = result;
if (resultConsumer != null)
resultConsumer.accept(result);
} }
public abstract String getId(); public TaskResult<V> storeTo(Consumer<V> resultConsumer) {
this.resultConsumer = resultConsumer;
return this;
}
public <R, E extends Exception> TaskResult<R> thenTaskResult(ExceptionalFunction<V, TaskResult<R>, E> taskSupplier) {
return new TaskResult<R>() {
TaskResult<R> then;
@Override
public Collection<? extends Task> getDependents() {
return Collections.singleton(TaskResult.this);
}
@Override
public void execute() throws Exception {
then = taskSupplier.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> Task then(ExceptionalFunction<V, Task, E> taskSupplier) {
return new CoupleTask(this, () -> taskSupplier.apply(getResult()), true);
}
public <R, E extends Exception> TaskResult<R> thenResult(ExceptionalFunction<V, R, E> task) { public <R, E extends Exception> TaskResult<R> thenResult(ExceptionalFunction<V, R, E> task) {
return thenResult(Schedulers.defaultScheduler(), task); return thenResult(Schedulers.defaultScheduler(), task);
} }
public <R, E extends Exception> TaskResult<R> thenResult(Scheduler scheduler, ExceptionalFunction<V, R, E> task) { public <R, E extends Exception> TaskResult<R> thenResult(Scheduler scheduler, ExceptionalFunction<V, R, E> task) {
return thenResult(ReflectionHelper.getCaller().toString(), scheduler, task); return thenResult(getCaller(), scheduler, task);
} }
public <R, E extends Exception> TaskResult<R> thenResult(String id, Scheduler scheduler, ExceptionalFunction<V, R, E> task) { public <R, E extends Exception> TaskResult<R> thenResult(String name, Scheduler scheduler, ExceptionalFunction<V, R, E> task) {
return new Subtask<>(id, scheduler, task); return new Subtask<>(name, scheduler, task);
} }
public <T extends Exception, K extends Exception> Task finalizedResult(Scheduler scheduler, ExceptionalConsumer<V, T> success, ExceptionalConsumer<Exception, K> failure) { // stupid javac stop us from renaming thenVoid to thenResult
return finalized(scheduler, variables -> success.accept(getResult()), failure); public <E extends Exception> Task thenVoid(ExceptionalConsumer<V, E> task) {
return thenVoid(Schedulers.defaultScheduler(), task);
} }
public Task finalizedResult(Scheduler scheduler, FinalizedCallback<V> callback) { public <E extends Exception> Task thenVoid(Scheduler scheduler, ExceptionalConsumer<V, E> task) {
return new FinalizedTask(this, scheduler, return new CoupleTask(this, () -> Task.of(scheduler, () -> task.accept(getResult())), true);
(variables, isDependentsSucceeded, exception) -> callback.execute(getResult(), isDependentsSucceeded, exception), }
ReflectionHelper.getCaller().toString());
public <T extends Exception, K extends Exception> Task finalized(Scheduler scheduler, ExceptionalConsumer<V, T> success, ExceptionalConsumer<Exception, K> failure) {
return finalized(scheduler, () -> success.accept(getResult()), failure);
}
public Task finalized(Scheduler scheduler, FinalizedCallback<V> callback) {
return finalized(scheduler, ((isDependentsSucceeded, exception) -> callback.execute(getResult(), isDependentsSucceeded, exception)));
} }
private class Subtask<R> extends TaskResult<R> { private class Subtask<R> extends TaskResult<R> {
private final String id;
private final Scheduler scheduler; private final Scheduler scheduler;
private final ExceptionalFunction<V, R, ?> callable; private final ExceptionalFunction<V, R, ?> callable;
public Subtask(String id, Scheduler scheduler, ExceptionalFunction<V, R, ?> callable) { public Subtask(String name, Scheduler scheduler, ExceptionalFunction<V, R, ?> callable) {
this.id = id;
this.scheduler = scheduler; this.scheduler = scheduler;
this.callable = callable; this.callable = callable;
setName(name);
} }
@Override @Override
@@ -81,11 +125,6 @@ public abstract class TaskResult<V> extends Task {
return Collections.singleton(TaskResult.this); return Collections.singleton(TaskResult.this);
} }
@Override
public String getId() {
return id;
}
@Override @Override
public Scheduler getScheduler() { public Scheduler getScheduler() {
return scheduler; return scheduler;

View File

@@ -17,17 +17,28 @@
*/ */
package org.jackhuang.hmcl.util; package org.jackhuang.hmcl.util;
import java.util.function.Predicate;
/** /**
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class ReflectionHelper { 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(); 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]; StackTraceElement caller = elements[2];
for (int i = 3; i < elements.length; ++i) { 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 elements[i];
} }
return caller; return caller;

View File

@@ -116,14 +116,43 @@ public final class FileUtils {
return new String(Files.readAllBytes(file), charset); 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 { public static void writeText(File file, String text) throws IOException {
writeText(file, text, UTF_8); 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 { public static void writeText(File file, String text, Charset charset) throws IOException {
writeBytes(file, text.getBytes(charset)); 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 { public static void writeBytes(File file, byte[] array) throws IOException {
Files.createDirectories(file.toPath().getParent()); Files.createDirectories(file.toPath().getParent());
Files.write(file.toPath(), array); 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 { public static void copyDirectory(Path src, Path dest) throws IOException {
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){ Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
@Override @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) { public static boolean moveToTrash(File file) {
try { try {
java.awt.Desktop desktop = java.awt.Desktop.getDesktop(); java.awt.Desktop desktop = java.awt.Desktop.getDesktop();
@@ -314,20 +365,4 @@ public final class FileUtils {
result.add(it); result.add(it);
return result; 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);
}
} }

View File

@@ -21,8 +21,9 @@ import java.io.*;
import java.nio.charset.Charset; 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 { public final class IOUtils {
@@ -31,12 +32,26 @@ public final class IOUtils {
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024; 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 { public static byte[] readFullyWithoutClosing(InputStream stream) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream(); ByteArrayOutputStream result = new ByteArrayOutputStream();
copyTo(stream, result); copyTo(stream, result);
return result.toByteArray(); 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 { public static ByteArrayOutputStream readFully(InputStream stream) throws IOException {
try (InputStream is = stream) { try (InputStream is = stream) {
ByteArrayOutputStream result = new ByteArrayOutputStream(); ByteArrayOutputStream result = new ByteArrayOutputStream();