Skip to content

Commit 9c6f2fc

Browse files
author
Z User
committed
fix: 修复顶部进度条卡死问题
- FocusManager添加releaseFocus方法 - ParallelDownloader在所有进度回调中添加acquireFocus调用 - ChunkedDownloader在UI更新和验证中添加acquireFocus调用
1 parent d225f5c commit 9c6f2fc

3 files changed

Lines changed: 81 additions & 9 deletions

File tree

src/main/java/com/github/balloonupdate/mcpatch/client/ChunkedDownloader.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ public void download(TempUpdateFile file) throws McpatchBusinessException {
199199
}
200200

201201
Log.info("分片下载完成: " + file.path);
202+
// 注意:焦点释放由 ParallelDownloader.doParallelDownload() 的 finally 块统一处理
202203
}
203204

204205
/**
@@ -407,6 +408,8 @@ private void mergeChunks(TempUpdateFile file, List<FileChunk> chunks, String fil
407408

408409
// 更新单文件进度条为合并状态(仅焦点文件)
409410
// 注意:焦点判断使用原始 filename,显示名称附加"(合并中)"后缀
411+
// [修复] 合并前重新获取焦点并刷新锁定
412+
acquireFocus(filename);
410413
if (window != null && isFocusFile(filename)) {
411414
final String displayName = getDisplayName(filename) + " (合并中)";
412415
SwingUtilities.invokeLater(() -> {
@@ -492,6 +495,8 @@ private void verifyMergedFile(TempUpdateFile file, String filename) throws Mcpat
492495

493496
// 更新单文件进度条为校验状态(仅焦点文件)
494497
// 注意:焦点判断使用原始 filename,显示名称附加"(校验中)"后缀
498+
// [修复] 校验前重新获取焦点并刷新锁定
499+
acquireFocus(filename);
495500
if (window != null && isFocusFile(filename)) {
496501
final String displayName = getDisplayName(filename) + " (校验中)";
497502
SwingUtilities.invokeLater(() -> {
@@ -542,6 +547,9 @@ private void updateVerifyProgress(String filename, long bytesRead, long totalFil
542547
if (now - uiTimer.get() <= 300) return;
543548
uiTimer.set(now);
544549

550+
// [修复] 校验进度回调中也刷新焦点锁定,防止校验期间焦点被抢占
551+
acquireFocus(filename);
552+
545553
final long br = bytesRead;
546554
final long tb = totalFileBytes;
547555
final String speedStr = speed.sampleSpeed2();
@@ -621,6 +629,11 @@ private void updateUI(String filename, long fileDownloaded, long fileSize) {
621629
}
622630
uiTimer.set(now);
623631

632+
// [修复] 在进度回调中重试获取焦点
633+
// 如果焦点文件已完成并释放了焦点,此文件可以接管单文件进度条
634+
// 如果此文件已是焦点文件,acquireFocus 会刷新锁定时间
635+
acquireFocus(filename);
636+
624637
final String speedStr = speed.sampleSpeed2();
625638
final long fd = fileDownloaded;
626639
final String displayName = getDisplayName(filename);

src/main/java/com/github/balloonupdate/mcpatch/client/FocusManager.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
* 设计背景:多线程下载时,如果每个线程都更新单文件进度条,文件名会疯狂闪烁。
1313
* 焦点机制只让一个"焦点文件"更新单文件进度条,其他文件只更新总进度。
1414
* 焦点锁定2秒,期间不会被其他文件抢占。
15+
* <p>
16+
* 焦点生命周期:
17+
* 1. 文件开始下载时调用 acquireFocus() 尝试获取焦点
18+
* 2. 焦点文件在进度回调中调用 acquireFocus() 刷新锁定时间,保持焦点
19+
* 3. 文件下载完成(或失败)时调用 releaseFocus() 释放焦点
20+
* 4. 其他文件的进度回调中调用 acquireFocus() 可在焦点释放后接管
1521
*/
1622
public class FocusManager {
1723

@@ -37,7 +43,12 @@ public FocusManager() {
3743
}
3844

3945
/**
40-
* 尝试获取焦点。如果当前没有焦点文件或锁定时间已过,则成为焦点文件。
46+
* 尝试获取焦点。分三种情况:
47+
* <ol>
48+
* <li>当前无焦点文件 → 获取焦点</li>
49+
* <li>调用者已是焦点文件 → 刷新锁定时间(保持焦点不丢失)</li>
50+
* <li>焦点锁定时间已过 → 抢占焦点</li>
51+
* </ol>
4152
* <p>
4253
* 使用 synchronized 保证 check-then-act 的原子性,避免 TOCTOU 竞态条件。
4354
*
@@ -46,11 +57,37 @@ public FocusManager() {
4657
public void acquireFocus(String filename) {
4758
synchronized (focusLock) {
4859
long now = System.currentTimeMillis();
49-
// 没有焦点文件,或者焦点锁定时间已过,可以抢占
50-
if (focusFilename == null || now - focusLockTime.get() > FOCUS_LOCK_DURATION) {
60+
if (focusFilename == null) {
61+
// 无焦点文件,直接获取
62+
this.focusFilename = filename;
63+
focusLockTime.set(now);
64+
} else if (filename.equals(focusFilename)) {
65+
// 已是焦点文件,刷新锁定时间
66+
focusLockTime.set(now);
67+
} else if (now - focusLockTime.get() > FOCUS_LOCK_DURATION) {
68+
// 锁定时间已过,抢占焦点
5169
this.focusFilename = filename;
5270
focusLockTime.set(now);
5371
}
72+
// 否则:焦点被其他文件持有且锁定未过期,获取失败(静默)
73+
}
74+
}
75+
76+
/**
77+
* 释放焦点。当焦点文件下载完成(或失败)时调用,允许其他文件抢占焦点。
78+
* <p>
79+
* 仅当调用者恰好是当前焦点文件时才执行释放,否则为空操作。
80+
* 释放后立即将 focusFilename 置为 null 并重置锁定时间,
81+
* 确保下一个调用 acquireFocus() 的文件能立即获得焦点,无需等待锁定过期。
82+
*
83+
* @param filename 要释放焦点的文件名
84+
*/
85+
public void releaseFocus(String filename) {
86+
synchronized (focusLock) {
87+
if (filename.equals(focusFilename)) {
88+
this.focusFilename = null;
89+
this.focusLockTime.set(0);
90+
}
5491
}
5592
}
5693

src/main/java/com/github/balloonupdate/mcpatch/client/ParallelDownloader.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public class ParallelDownloader {
5959
private final int totalFileCount;
6060

6161
/**
62-
* 已完成的文件数(线程安全)
62+
* 已成功完成的文件数(线程安全,仅下载成功时递增
6363
*/
6464
private final AtomicInteger completedFileCount = new AtomicInteger(0);
6565

@@ -170,22 +170,30 @@ private List<DownloadResult> doParallelDownload(List<TempUpdateFile> files, int
170170
// 提交下载任务
171171
List<Future<?>> futures = new ArrayList<>();
172172
for (TempUpdateFile f : files) {
173+
// 在外层计算 filename,确保 finally 块中可以访问以释放焦点
174+
final String filename = PathUtility.getFilename(f.path);
173175
futures.add(executor.submit(() -> {
174176
focusManager.incrementActiveDownloadCount();
177+
boolean success = false;
175178
try {
176179
// 判断是否需要分片下载
177180
if (chunkedDownloader.shouldUseChunkedDownload(f)) {
178181
chunkedDownloader.download(f);
179182
} else {
180183
downloadSingleFile(f);
181184
}
185+
success = true;
182186
} catch (Exception e) {
183187
failures.add(new DownloadResult(f, e));
184188
} finally {
185189
focusManager.decrementActiveDownloadCount();
186-
// 文件完成(无论成功失败都算处理完毕)
187-
int completed = completedFileCount.incrementAndGet();
188-
updateStatusUI(completed);
190+
// 释放焦点,让其他正在下载的文件有机会接管单文件进度条
191+
focusManager.releaseFocus(filename);
192+
// 仅成功完成时递增计数(失败文件将在重试轮次中重新计数)
193+
if (success) {
194+
int completed = completedFileCount.incrementAndGet();
195+
updateStatusUI(completed);
196+
}
189197
}
190198
}));
191199
}
@@ -224,9 +232,9 @@ private void updateStatusUI(int completedFiles) {
224232
* 每次调用会创建独立的 ServerSession,确保线程安全。
225233
*/
226234
private void downloadSingleFile(TempUpdateFile f) throws Exception {
227-
try (ServerSession session = servers.createSession()) {
228-
String filename = PathUtility.getFilename(f.path);
235+
String filename = PathUtility.getFilename(f.path);
229236

237+
try (ServerSession session = servers.createSession()) {
230238
Log.debug(" 开始下载 " + f.path + " (线程: " + Thread.currentThread().getName() + ")");
231239
Log.debug(" Download " + f.tempPath);
232240

@@ -271,6 +279,10 @@ private void downloadSingleFile(TempUpdateFile f) throws Exception {
271279
if (now2 - uiTimer.get() > 300) {
272280
uiTimer.set(now2);
273281

282+
// [修复] 在进度回调中重试获取焦点
283+
// 如果焦点文件已完成并释放了焦点,此文件可以接管单文件进度条
284+
acquireFocus(filename);
285+
274286
final String speedStr = speed.sampleSpeed2();
275287
final long fileDownloaded = bytesCounter.get();
276288
final boolean isFocus = isFocusFile(filename);
@@ -299,6 +311,9 @@ private void downloadSingleFile(TempUpdateFile f) throws Exception {
299311

300312
if (window != null) {
301313
final String speedStr = speed.sampleSpeed2();
314+
315+
// [修复] OnFail回调中也尝试获取焦点,确保回退进度能正确显示
316+
acquireFocus(filename);
302317
final boolean isFocus = isFocusFile(filename);
303318

304319
SwingUtilities.invokeLater(() -> {
@@ -324,6 +339,8 @@ private void downloadSingleFile(TempUpdateFile f) throws Exception {
324339
// 显示校验状态(仅焦点文件)
325340
// 注意:校验阶段使用原始filename做焦点判断,而非verifyLabel,
326341
// 因为focusFilename存储的是原始文件名(不含"(校验中)"后缀)
342+
// [修复] 校验前重新获取焦点并刷新锁定
343+
acquireFocus(filename);
327344
if (window != null && isFocusFile(filename)) {
328345
final String displayName = getDisplayName(filename) + " (校验中)";
329346
SwingUtilities.invokeLater(() -> {
@@ -354,6 +371,8 @@ private void downloadSingleFile(TempUpdateFile f) throws Exception {
354371
}
355372
}
356373
}
374+
// 注意:焦点释放由 doParallelDownload() 的 finally 块统一处理,
375+
// 而非在此处释放,确保校验阶段焦点不会被提前释放
357376
}
358377

359378
/**
@@ -367,6 +386,9 @@ private void updateVerifyProgress(String filename, long bytesRead, long totalFil
367386
if (now - uiTimer.get() <= 300) return;
368387
uiTimer.set(now);
369388

389+
// [修复] 校验进度回调中也刷新焦点锁定,防止校验期间焦点被抢占
390+
acquireFocus(filename);
391+
370392
final long br = bytesRead;
371393
final long tb = totalFileBytes;
372394
final String speedStr = speed.sampleSpeed2();

0 commit comments

Comments
 (0)