diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index b3b3f1f0c..6893612de 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class TaskListPane extends StackPane { @@ -172,12 +173,24 @@ public final class TaskListPane extends StackPane { node.setThrowable(throwable); }); } + + @Override + public void onPropertiesUpdate(Map> stageProperties) { + stageProperties.forEach((stage, properties) -> { + int count = tryCast(properties.get("count"), Integer.class).orElse(0), + total = tryCast(properties.get("total"), Integer.class).orElse(0); + if (total > 0) + Platform.runLater(() -> + stageNodes.stream().filter(x -> x.stage.equals(stage)).findAny().ifPresent(stageNode -> stageNode.updateCounter(count, total))); + }); + } }); } - private class StageNode extends BorderPane { + private static class StageNode extends BorderPane { private final String stage; private final Label title = new Label(); + private final String message; private boolean started = false; public StageNode(String stage) { @@ -185,7 +198,6 @@ public final class TaskListPane extends StackPane { String stageKey = StringUtils.substringBefore(stage, ':'); String stageValue = StringUtils.substringAfter(stage, ':'); - String message; // @formatter:off switch (stageKey) { @@ -222,6 +234,13 @@ public final class TaskListPane extends StackPane { public void succeed() { setLeft(FXUtils.limitingSize(SVG.check(Theme.blackFillBinding(), 14, 14), 14, 14)); } + + public void updateCounter(int count, int total) { + if (total > 0) + title.setText(String.format("%s - %d/%d", message, count, total)); + else + title.setText(message); + } } private class ProgressListNode extends BorderPane { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index 8d535a29b..1beb6c494 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -78,7 +78,8 @@ public class VersionPage extends Control implements DecoratorPage { Profiles.registerVersionsListener(this::loadVersions); listView.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> { - loadVersion(newValue, profile); + if (newValue != null) + loadVersion(newValue, profile); }); versionSettingsTab.setNode(versionSettingsPage); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java index ed2b1827c..6f698be2e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java @@ -123,13 +123,17 @@ public final class GameAssetDownloadTask extends Task { .setCacheRepository(dependencyManager.getCacheRepository()) .setCaching(true) .setCandidate(dependencyManager.getCacheRepository().getCommonDirectory() - .resolve("assets").resolve("objects").resolve(assetObject.getLocation()))); + .resolve("assets").resolve("objects").resolve(assetObject.getLocation())).withCounter()); } else { dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash()); } updateProgress(++progress, index.getObjects().size()); } + + if (!dependencies.isEmpty()) { + getProperties().put("total", dependencies.size()); + } } public static final boolean DOWNLOAD_INDEX_FORCIBLY = true; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java index 988e04d32..582556dff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java @@ -151,9 +151,13 @@ public final class CurseCompletionTask extends Task { if (!modManager.hasSimpleMod(file.getFileName())) { dependencies.add(new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile()) .setCacheRepository(dependency.getCacheRepository()) - .setCaching(true)); + .setCaching(true).withCounter()); } } + + if (!dependencies.isEmpty()) { + getProperties().put("total", dependencies.size()); + } } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CancellableTaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CancellableTaskExecutor.java index ad7e076c0..d64c89362 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CancellableTaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CancellableTaskExecutor.java @@ -20,11 +20,10 @@ package org.jackhuang.hmcl.task; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.function.ExceptionalRunnable; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.UnaryOperator; import java.util.logging.Level; public class CancellableTaskExecutor extends TaskExecutor { @@ -105,6 +104,19 @@ public class CancellableTaskExecutor extends TaskExecutor { return success.get() && !cancelled.get(); } + private synchronized void updateStageProperties(String stage, Map taskProperties) { + stageProperties.putIfAbsent(stage, new HashMap<>()); + Map prop = stageProperties.get(stage); + for (Map.Entry entry : taskProperties.entrySet()) { + if (entry.getValue() instanceof UnaryOperator) { + prop.put(entry.getKey(), ((UnaryOperator) entry.getValue()).apply(prop.get(entry.getKey()))); + } else { + prop.put(entry.getKey(), entry.getValue()); + } + } + taskListeners.forEach(taskListener -> taskListener.onPropertiesUpdate(stageProperties)); + } + private boolean executeTask(Task parentTask, Task task) { task.setCancelled(this::isCancelled); @@ -161,6 +173,10 @@ public class CancellableTaskExecutor extends TaskExecutor { task.setState(Task.TaskState.EXECUTED); } + if (task.properties != null) { + updateStageProperties(task.getStage(), task.properties); + } + Collection> dependencies = task.getDependencies(); boolean doDependenciesSucceeded = executeTasks(task, dependencies); Exception dependenciesException = dependencies.stream().map(Task::getException) @@ -191,6 +207,10 @@ public class CancellableTaskExecutor extends TaskExecutor { Logging.LOG.log(Level.FINER, "Task finished: " + task.getName()); } + if (task.properties != null) { + updateStageProperties(task.getStage(), task.properties); + } + task.onDone().fireEvent(new TaskEvent(this, task, false)); taskListeners.forEach(it -> it.onFinished(task)); } catch (RejectedExecutionException e) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java index 3ab6383d2..ddb02d7c7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java @@ -32,15 +32,13 @@ import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalRunnable; import org.jackhuang.hmcl.util.function.ExceptionalSupplier; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -107,6 +105,13 @@ public abstract class Task { return getStage() == null ? Collections.emptyList() : Collections.singletonList(getStage()); } + Map properties; + + protected Map getProperties() { + if (properties == null) properties = new HashMap<>(); + return properties; + } + // state private TaskState state = TaskState.READY; @@ -802,6 +807,10 @@ public abstract class Task { }; } + public Task withCounter() { + return new CountTask(); + } + public static Task runAsync(ExceptionalRunnable closure) { return runAsync(Schedulers.defaultScheduler(), closure); } @@ -1063,4 +1072,37 @@ public abstract class Task { return Lang.merge(Task.this.getStages(), super.getStages()); } } + + private class CountTask extends Task { + private final UnaryOperator COUNTER = a -> { + int result = 0; + if (a != null) result += a; + return result + 1; + }; + + @Override + public Collection> getDependents() { + return Collections.singleton(Task.this); + } + + @Override + public void execute() throws Exception { + setResult(Task.this.getResult()); + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() { + getProperties().put("count", COUNTER); + } + + @Override + public List getStages() { + return Lang.merge(Task.this.getStages(), super.getStages()); + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java index 3bcb88f3f..5b0b8b076 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java @@ -19,8 +19,10 @@ package org.jackhuang.hmcl.task; import org.jetbrains.annotations.Nullable; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -31,6 +33,7 @@ public abstract class TaskExecutor { protected final AtomicBoolean cancelled = new AtomicBoolean(false); protected Exception exception; private final List stages; + protected final Map> stageProperties = new HashMap<>(); public TaskExecutor(Task task) { this.firstTask = task; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java index b36753418..36c876b1b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.task; import java.util.EventListener; +import java.util.Map; /** * @@ -78,4 +79,7 @@ public abstract class TaskListener implements EventListener { */ public void onStop(boolean success, TaskExecutor executor) { } + + public void onPropertiesUpdate(Map> stageProperties) { + } }