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

@@ -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)
.thenTaskResult(() -> 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
.thenTaskResult(LibrariesUniqueTask::new)
.thenTaskResult(MaintainTask::new)
.thenTaskResult(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);
}
}

View File

@@ -21,6 +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.task.TaskResult;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -49,34 +50,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).thenTaskResult(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.thenResult(() -> version);
if (toolVersions.containsKey("forge"))
result = result.then(libraryTaskHelper(gameVersion, "forge"));
libraryTask = libraryTask.thenTaskResult(libraryTaskHelper(gameVersion, "forge"));
if (toolVersions.containsKey("liteloader"))
result = result.then(libraryTaskHelper(gameVersion, "liteloader"));
libraryTask = libraryTask.thenTaskResult(libraryTaskHelper(gameVersion, "liteloader"));
if (toolVersions.containsKey("optifine"))
result = result.then(libraryTaskHelper(gameVersion, "optifine"));
libraryTask = libraryTask.thenTaskResult(libraryTaskHelper(gameVersion, "optifine"));
for (RemoteVersion remoteVersion : remoteVersions)
result = result.then(dependencyManager.installLibraryAsync(remoteVersion));
libraryTask = libraryTask.thenTaskResult(dependencyManager.installLibraryAsync(remoteVersion));
return result;
}).finalized((variables, isDependentsSucceeded, exception) -> {
return libraryTask;
}).finalized((isDependentsSucceeded, exception) -> {
if (!isDependentsSucceeded)
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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -23,13 +23,12 @@ 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;
@@ -134,16 +133,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;
}
@@ -280,16 +269,8 @@ public abstract class Task {
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));
subscribe(of(scheduler, closure));
}
public final void subscribe(ExceptionalRunnable<?> closure) {
@@ -300,16 +281,41 @@ public abstract class Task {
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);
}
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) {
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) {
@@ -317,15 +323,16 @@ public abstract class Task {
}
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) {
return finalized(scheduler, (variables, isDependentsSucceeded, exception) -> {
// T, K here is necessary, or javac cannot infer type of failure
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 (success != null)
try {
success.accept(variables);
success.run();
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Failed to execute " + success, e);
if (failure != null)
@@ -338,58 +345,38 @@ public abstract class Task {
});
}
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;
}
@@ -421,4 +408,12 @@ public abstract class Task {
SUCCEEDED,
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 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) {
@@ -147,8 +146,6 @@ public final class TaskExecutor {
boolean flag = false;
try {
task.setVariables(variables);
if (task.doPreExecute()) {
try {
task.getScheduler().schedule(task::preExecute).get();
@@ -186,11 +183,6 @@ public final class TaskExecutor {
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();
boolean doDependenciesSucceeded = executeTasks(dependencies);
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));
taskListeners.forEach(it -> it.onFailed(task, e));
} finally {
task.setVariables(null);
}
task.setState(flag ? Task.TaskState.SUCCEEDED : Task.TaskState.FAILED);
return flag;
@@ -249,10 +239,6 @@ public final class TaskExecutor {
return totTask.get();
}
public AutoTypingMap<String> getVariables() {
return variables;
}
private class Invoker implements ExceptionalRunnable<Exception> {
private final Task task;

View File

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

View File

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

View File

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

View File

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