fix: base home sync progress on announced download total
Mirror Repository / Mirror to CNB (push) Skipped
Mirror Repository / Mirror to Gitee (push) Skipped
Java CI / build (pull_request) Canceled after 1m41s

The percentage used finished/seen task counts, which hit the 99% cap as soon
as the first tasks finished and then stayed there while new tasks were still
being discovered. Use the "total" property announced by the sync task as the
denominator and count finished file downloads; stay indeterminate until known.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiBPbVGBHNHdzqfq1L5tch
This commit is contained in:
layue13andClaude Sonnet 5.5 committed 2026-11-11 12:36:56 +08:00
1 parent 26187e6f61
commit bcefa87e27
1 file changed
+37 -16
@@ -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<Task<?>> 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);