Repository navigation
Alpha File Manager v3.5.0
English summary
3.5.0 fixes two things: "Compare and Sync" was effectively unusable on real
folders, and the delete progress bar was lying.
- Comparing two folders no longer gives up at the first unreadable directory.
Unreadable directories are skipped, the count is shown in the summary, and the
comparison carries on. Only an unreadable root is still an error. - Comparing a large tree is about fifty times faster. The metadata now comes
straight from the directory entry instead of rebuilding an absolute path, and
four workers collect in parallel. Measured over the same ten thousand entries:
1.18 s before, 23 ms after (measured in both orders, so neither side could
claim a warm cache). - The comparison now reports its progress — which side it is reading, how
many entries it has been through, and how far along the comparison itself is. - Deleting a big folder tree finally shows what it is doing. The progress
bar is driven by the number of items actually deleted, with the freed space
shown alongside. Cancelling is checked before every file, and up to four
independent subtrees are removed in parallel.
3.5.0:比较不再罢工,删大树时进度条终于说实话了
这一版修两件事:「比较并同步」在真实目录上基本用不了,以及批量删除的
进度条在骗人。两件都是用户在实际使用中撞上的,不是设想出来的。
第一件:比较并同步 —— 撞上一个读不了的目录,整个功能就废了
用户反馈的是「进度非常缓慢,最后变成一个报错页面」。查下来是两件事叠在一起。
报错的原因:任何一个读不了的子目录,就让整个比较失败
代码里收集目录树是这样写的:
let read = std::fs::read_dir(&dir_path)
.map_err(|error| format!("无法读取目录「{}」:{error}", dir_path.display()))?;任何一个子目录读不了,整个比较就返回错误。 而 Windows 上读不了的目录
太多了 —— System Volume Information、$RECYCLE.BIN、别人正占着的目录、
权限不足的用户目录。真实目录树上几乎必然有。
我在本机造了一个拒绝访问的目录,复现得很干净:
panicked: collect left: "无法读取目录「…\left\denied」:拒绝访问。 (os error 5)"
所以用户看到的「报错页面」不是什么玄学,是这个功能在真实目录上基本不可用。
现在读不了的目录被跳过,计数报出来(界面上显示「无法读取 N」),比较继续
走其它目录。根目录本身读不了才算真错误 —— 那是真的没法比。
慢的原因:每个条目的元数据查得太贵
收集阶段占了整轮比较的 93% 的时间(实测:收集 4.3 秒,判定 0.11 秒,
序列化 0.17 秒)。而收集慢在每一行代码上:
let Ok(metadata) = std::fs::symlink_metadata(item.path()) else { continue };item.path() 要为每个条目重新拼一次绝对路径,然后再拿这个路径去查一次
元数据。实测这一处就是瓶颈:
| 实现 | 收集 10,020 个条目 |
|---|---|
| 旧写法 | 1.18 秒 |
| 去掉「无条件缓存每个文件的绝对路径」 | 1.44 秒(没改善,不是元凶) |
改用 item.metadata()(目录项自带的元数据) |
35.7 毫秒 |
现在改用 item.metadata()(元数据直接从目录项取),并且四个线程并行收集
—— 这一步是 IO 密集的,单线程会让磁盘的请求队列一直空着。
两个顺序各测一遍(避免「谁先跑谁吃冷缓存」的偏袒):
| 顺序 | 旧实现 | 新实现 | 加速 |
|---|---|---|---|
| 旧→新 | 1.33 秒 | 29.6 毫秒 | 44.95× |
| 新→旧 | 22.9 毫秒 | 1.13 秒 | 49.54× |
一万个条目的目录树,收集一侧现在 23 毫秒;十万个条目的树,旧版要
11 秒,新版约 0.2 秒。
比较的时候终于有进度了
以前比较一棵大树,界面上只有一个转圈 —— 用户分不清它是在干活还是死了。
现在会显示:正在扫描左侧/右侧、已经扫过多少个条目、判定阶段比对了
多少个 / 共多少个。事件里只带结构化数据,文案由界面本地化,17 个语言包
都能说人话。
条目太多时,界面不会再被撑爆
结果树以前是一次性摊平成全部行再逐行渲染。十万个条目就是十万个 DOM
节点,窗口会胀到操作不动。
现在一次只渲染前 2000 行,剩下的按「再显示一批」放出来,并且始终明说
一共多少条 —— 不让用户以为看到的就是全部。统计数字(共多少条、多少有
差异)本来就是全量算的,不受影响;同步计划的计算也在后端自己重新比较,
不受影响。
内容校验和不再把整个文件读进内存
开了「按内容校验和」这一项,以前用 std::fs::read 把每个文件整个读进
内存 —— 几百 MB 的安装包就足以把进程撑爆。现在按 64 KB 的缓冲流式读,
内存占用与文件大小无关;而且摘要和收集一样是并行的。
比较这一块现在也有日志了
以前 compare 模块一行日志都不写,出了问题只能靠猜。现在会记:比较
开始了(两侧路径 + 准则)、每侧收集到多少条目 / 跳过多少 / 耗时多久、
判定结果、总共多少条差异。应用日志在
%LOCALAPPDATA%\com.sigma-file-manager.app\logs\Alpha File Manager.log。
第二件:删大树时进度条终于说实话了,取消也真的能停
这一版修的是批量删除。触发它的是一份 2026-10-22 的分析报告:删一个
25 万文件的目录树,进度条长时间停在 4%,用户判断为「卡死」。
先说结论:删除逻辑一直是对的,没卡死、没死锁、没丢文件。问题出在两处 ——
进度条在骗人,以及删除吞吐偏低。这一版把两处都动了。
先说清楚一件事:报告的归因是错的
报告说「进度按字节计量,所以小文件多的时候失真」。翻代码发现不是这样:
本项目的删除进度一直按顶层路径数推进,字节数从来没进过进度公式。
症状一样(进度条不动),根因不同,修法也不同 —— 不是换计量单位,是得逐条目
数。这一条如果搞错了,改出来的东西看着对、实际还是骗人。
进度条现在按条目数走
删一棵含 25 万文件的目录树,进度条会随着文件一个个被删掉稳定往前走,
不再是「4% 卡 40 分钟」。界面上同时给两个口径,并明确标注:
- 已删除 248,332 / 250,053 项(99.3%) —— 主指标
- 已释放 55.43 GiB / 72.06 GiB(76.9%) —— 辅助指标
统计阶段(刚开工、总数还没数完)走不确定态,不报百分比 ——
给不出「离完成还有多远」时就不该假装能给出。
取消真的能停
以前点「取消」只在顶层路径之间检查,所以删一个大目录时点了取消,
也得等那个目录整个删完才生效。现在每个文件之前都检查一次,
点下去最多再删几十个文件就停。
删之前先量一遍规模
删除开始前先把要删的树走一遍,拿到「N 个文件 / M 个目录 / X GiB」。
这给了进度条一个真实的分母(而不是猜的),也让用户点下删除之前就知道
自己在删什么。超过 1 万个条目时,界面上会直接把规模摆出来。
代价是多走一遍目录树。对机械硬盘来说这是实打实的 I/O,
但换来的「进度条不说谎 + 随时能停」值这个价。
子目录并行删
机械硬盘删海量小文件是寻道瓶颈:磁头一次只能服务一个位置。
单线程串行删的时候,磁盘队列长度长期只有 0.75 —— 磁盘根本没被喂饱。
现在同一目录下的各个子目录分给最多 4 个线程并行删除。几个互不依赖的
子树同时删,磁头才能同时寻道多个位置。
顺带修掉的:安装与更新链路
用户反馈「装完 3.4.0 又装了一次,感觉像是激活了在线更新」。查下来:
- 扩展的更新检查,以前一次都没成功过。 它打的是
github.com/.../tags.atom—— 这个地址在国内网络根本连不通(本机实测:
github.com 直连 20 秒超时,api.github.com 稳定 200)。现在改走
api.github.com(应用自身的更新器早就在用它了),原地址留作兜底。
顺带说明:扩展的更新检查第一次真正能用。 - 更新检查与应用器都有日志了。 更新检查每次都记下:装的是哪个版本、
服务器报的是哪个版本、哪个源答的、判定了有没有更新、报了什么错。
安装器会写一份安装日志(%TEMP%\alpha-file-manager-install.log):本次装的
版本、检测到的既有安装版本与位置、这次是不是同版本重装、最终选定的
安装目录、四个阶段各自的结论。
这一版是怎么验的
- 比较引擎的回归测试直接复现了那个报错:造一个拒绝访问的目录,
断言比较继续而不是失败,断言读不到的条目数被报出来。 - 性能是量出来的,不是估的:旧实现照抄进基准里,和新实现在同一批文件上
对照,两个顺序各测一遍。 - 删除核心(
delete_tree.rs)刻意不引用 tauri,所以算法本体有单测:
计数、字节口径、取消的及时性、并行删除的完整性、进度单调性、节流闸门。 - 跨语言契约门禁: Rust 的进度事件字段与 TS 的接口逐条对得上
(写错一个字母,两边单测照样全绿,真机上进度条就成了 0% 到底)。 - 真实机器上用打包产物验:删除进度随条目推进、取消立刻生效、并行删除不留残渣。
这个版本不是什么
- 没有 ETA。 速率外推在机械硬盘上抖动很大,给一个看似精确的数字反而不如不给。
- 比较结果仍然是「先全部算完再显示」。 这一版做的是不撑爆界面,
没有改成边算边出。 - 没有内容校验和、没有镜像模式。 与 3.4.0 同。
- 没有超过阈值时的二次确认框。 规模已经摆出来了,但「再问一次才动手」
这个交互这版没做。
构建信息
- 版本号:
3.5.0 - 安装包:
Alpha_File_Manager_3.5.0_x64-setup.exe - 大小:20494128 字节
- SHA-256:
76f3a5957c4626378e1de53cafc1ca56331cfb689c163c14e168afe45f895ef4