diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ClientSyncProgress.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ClientSyncProgress.java index 156ee2b5b2..635587faf8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ClientSyncProgress.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ClientSyncProgress.java @@ -4,6 +4,7 @@ package org.jackhuang.hmcl.ui.instances; import javafx.beans.property.*; import org.jackhuang.hmcl.task.FetchTask; +import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskListener; @@ -19,8 +20,7 @@ import java.util.function.Consumer; /// /// The model is only a view of the task executor started by [Instances#ensureClientAndJoin(ClientSyncProgress)]: /// it never changes what is downloaded. All properties must be read and written on the JavaFX thread. -/// Overall percentage is estimated from the number of finished tasks, so it never moves backwards but -/// may advance unevenly while new tasks are discovered. +/// Overall percentage is estimated from finished file downloads against the total announced by the sync task, and stays indeterminate until that total is known. @NotNullByDefault public final class ClientSyncProgress { @@ -53,8 +53,12 @@ public final class ClientSyncProgress { private @Nullable TaskExecutor executor; private long downloadedBytes; - private int seenTasks; - private int finishedTasks; + /// Total number of downloads announced by tasks so far. + private int expectedFiles; + /// Number of finished file downloads. + private int finishedFiles; + /// Tasks whose `total` has already been added to [#expectedFiles]. + private final java.util.Set> announcedTasks = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); private boolean cancelled; /// Keeps the weakly registered speed listener alive. @@ -101,8 +105,9 @@ public final class ClientSyncProgress { executor = null; cancelled = false; downloadedBytes = 0; - seenTasks = 0; - finishedTasks = 0; + expectedFiles = 0; + finishedFiles = 0; + announcedTasks.clear(); progress.set(-1); detail.set(""); error.set(""); @@ -149,6 +154,15 @@ public final class ClientSyncProgress { return cancelled; } + /// Recomputes the fraction from finished downloads; stays indeterminate until a total is known. + private void refreshProgress(TaskExecutor current) { + if (executor != current || expectedFiles <= 0) + return; + double value = Math.min(0.99, (double) finishedFiles / expectedFiles); + if (value > progress.get()) + progress.set(value); + } + /// Submits `task` to the serial task queue and mirrors its task count into the progress. /// /// @param task the task to run @@ -156,21 +170,28 @@ public final class ClientSyncProgress { TaskExecutor taskExecutor = task.executor(); executor = taskExecutor; taskExecutor.addTaskListener(new TaskListener() { + /// Tasks announce how many downloads they will start through the `total` property + /// (set once the file list is known), so the denominator never grows with discovered tasks. @Override - public void onReady(Task ready) { - FXUtils.runInFX(() -> seenTasks++); + public void onPropertiesUpdate(Task updated) { + Object total = updated.getProperties().get("total"); + if (total instanceof Integer count && count > 0) { + FXUtils.runInFX(() -> { + if (announcedTasks.add(updated)) + expectedFiles += count; + refreshProgress(taskExecutor); + }); + } } @Override public void onFinished(Task finishedTask) { - FXUtils.runInFX(() -> { - finishedTasks++; - if (executor != taskExecutor || seenTasks <= 0) - return; - double value = Math.min(0.99, (double) finishedTasks / seenTasks); - if (value > progress.get()) - progress.set(value); - }); + if (finishedTask instanceof FileDownloadTask) { + FXUtils.runInFX(() -> { + finishedFiles++; + refreshProgress(taskExecutor); + }); + } } }); TaskManager.getInstance().submit(taskExecutor, null);