Repository navigation
Releases: Mofan2020/SuperDownloadManager
Release list
v2.2.4
v2.2.4
这一版修的是下载过程中文件内容错乱的问题。
下载的文件内容错乱
下载完成的文件可能大小正常但内容是错的。7z、APK、DMG 这几种格式打开都会报「数据已损坏」。
- 动态分块时同一个块只会被一个连接下载,不会出现两个连接往同一块里写、互相覆盖的情况。
- 下载器会核对服务器实际发来的字节数。跟你请求的不一致时会报错,不会把不对的数据当成下载好的。
- 合并文件之前会核对每一块的长度。块缺失或者长度不对时会报错,不会拼出一个大小对但内容错的文件。
- 取消下载或者下载失败后残留的临时分片文件会被彻底清掉,不会被下一次下载当成「已经下好的内容」。
速度显示
- 续传的任务刚启动时,速度曲线不再飙到几万 MB/s。那个数字是假的,是把盘上已有的字节算成了这一瞬间的传输量。
- 详情页每个分片的「耗时」显示的是这个分片实际下载了多久。之前显示的是「距上次写入多久」,已完成的分片那个数字会一直涨。
详情页分片列表
- 每个分片多一列「速率」,一眼能看出哪个分片拖后腿。
- 卡住的分片(下载中但长时间没进数据)整行标红。
- 进度条上悬停能看到每个分片的状态、字节数、耗时和速率。
v2.2.3
v2.2.3
修了你反馈的三个问题。
继续下载
失败之后再点「继续下载」,现在真的从断点接着下,不会从头重来。
- 服务器每次请求都换 ETag 的情况,不再被误判成「文件变了」。
- 文件确实变了的话,点一次「继续下载」就会自动清掉旧进度重新下,不用再点第二次。详情面板会写明「服务器上的文件已变化,已重新开始下载」。
- 任务刚失败、还在收尾的时候点「继续下载」,会等收尾完成再启动,不会「点了没反应」。
- 盘上残留的分片文件(取消过的下载、Windows 上删不掉的那些)不会再被当成「已经下好的字节」,新下载的内容不会被带歪。
链接失效
链接失效后可以填新链接继续。SDM 会先确认新链接拿到的文件和原来那个是同一个文件:
- 是同一个文件 → 接着下,已经下过的部分不重下。
- 不是 → 拒绝并提示换一个,已经下好的字节一个不动。
- 没法确认 → 清掉重新下,界面上说明进度没了。
判断靠逐字节比对。ETag 和文件大小都不靠谱:很多 CDN 每次请求都换 ETag,大小一样的文件更是到处都有。
「换链接」也进了右键菜单,失败、暂停、等待新链接的任务都能用。
详情页
分段明细跟着列表一起刷新了,不用关掉详情页再重开。刷新时滚动位置会保住。
Windows 上的坏文件
动态分段切出来的新分片,在 Windows 上可能把上一轮下载残留的字节当成「已经下好的」,新内容接在旧内容后面写,合并出来的文件长度看着正常、内容是乱的。现在从零写,不会再混进去。
v2.2.2
v2.2.2
五处修复,全是用户 2026-10-28 反馈的。
详情面板里的分片表一打开就没了
点进任务详情菜单,分片显示大概零点几秒内就消失了。
根因:分片明细挂在 task 对象上,而列表轮询每 1.5 秒把
State.tasks 整个换掉(列表接口不带分片明细,省内存 + 省序列化)——
挂在 task 上的 chunks 被这一换抹掉。
现在分片明细单独存 State.taskChunks[taskId],跟 taskErrors、
speedHistory 一个路子。任务移除时一起清掉。
辅助文件不再堆在下载目录
.sdm.json(完成后的元信息)、.sdm.session.json(下载中的会话)、
.sdm.partial-N(每个分片一个临时文件)以前都躺在目标文件旁边。
一个 16 分片的下载会在用户的下载文件夹里留下 19 个文件,而且
.sdm.partial-* 这种名字用户完全看不懂——只会以为软件下坏了。
现在统一搬到 SDM 自己的地盘:
~/Library/Application Support/SuperDownloadManager/downloads/
%LOCALAPPDATA%\SuperDownloadManager\downloads\ (Windows)
~/.local/share/SuperDownloadManager/downloads/ (Linux)
下载目录里从此只剩用户要的那一个文件。
文件名的 key 里带目标绝对路径的短哈希——同名文件放在不同目录是
不同文件,它们的 sidecar 不能混(sidecar 是用来比对 etag /
content_length 判断「还是不是同一个文件」的,混了会导致把新内容
续写进旧文件的空洞里)。
升级前下的东西还在老位置:读的时候会把旧的搬过来,所以正在下的
下载不会凭空消失,续传也用不上。
重名自动加编号,格式跟 macOS 一致
movie.mkv 已存在 → 下成 movie 1.mkv,再下成 movie 2.mkv。
即在文件名的末尾(扩展名之前)加一个空格,再加编号,编号表示
这是第几个同名文件。跟 Finder / Safari / 邮件客户端的处理方式一致,
用户不用适应第二套规则。以前是 movie (1).mkv(第三种风格)。
动态分段:把长尾治了
用户观察到的现象是「下载到 96% 的时候速度掉得很低,比刚开始低很多」。
这是长尾——下载末尾的活只剩一两条连接在爬。
根因有三个,叠在一起:
- 分片数封顶 = 目标分片数 = 连接数。 空闲 worker 想去切最大的
未完成分片,切不动 → worker 直接退出,那条连接就永久没了。 - 最小可拆大小固定不变。 剩下的字节不够再拆一半时,一片都拆不动。
- 「找活干」占着连接名额。 名额只在真正传输时才该占——不然
governor 限的是「已经分好片的数量」,不是「同时在传的连接数」。
修法:
- 分片数上限改成
max_chunks(连接数 × 4 或 32,取大的)——目标分片数
是连接数,不是分片数的硬上限。多出来的分片就是给空闲连接准备的
活干。 - 最小可拆大小随剩余量自适应:按「剩余总量 / 目标分片数」算出
平均每片该多大,夹在硬地板(64KB)和min_chunk_size之间。剩得越少
切得越细。 - worker 拆不动时不退出,改成等新分片(20ms 一轮)。
- 「找活干」不占连接名额。
顺带修了一个死设置:设置页的「初始连接数」和「分片上限」以前
界面能改、引擎压根不读,用户调完发现什么都没变——只能得出「这程序
没有动态分段能力」的结论。现在真的接到引擎上了,而且分片上限被强制
不低于连接数 × 4(否则长尾立刻回来)。
验证方式
尾部速度用结构性指标测,不用速度比值——比值受机器负载影响,
标不准阈值就是一条会随机红的 flaky 测试。
第一层:结构性的事实(完全不看时间,零波动)
- 没人 worker 在活没干完的时候提前下班
- worker 确实等过新分片
- 分片数突破了「目标分片数」= 连接数
第二层:差分比较(跟机器快慢无关)
同一个测试里跑两遍——一遍是现在的实现,一遍是「把分片数封顶改回
目标分片数」的旧行为——同一台机器、同一时刻,然后比较。
机器快慢在两边同时生效,抵消掉了。所以断言的是「现在的实现明显
好于旧行为」,而不是「现在的实现达到某个绝对值」。
这是 flaky 测试的根治办法:跟一个已知的坏实现比。
最初用的是绝对阈值(「尾段平均并发 ÷ 峰值并发 ≥ 0.70」),
结果 v2.2.2 的第一次 CI 就红了——CI runner 上峰值并发被算成 9
(本地是 8),比值 0.65 刚好掉到阈值下面。慢服务器下更差,
实测能掉到 0.66、连接数掉到 9 条。绝对阈值标不准就是随机红。
实测差距:
快服务器 好 13 条 / 0.85 vs 坏 9 条 / 0.41
慢服务器 好 10 条 / 0.80 vs 坏 7 条 / 0.21
变异测试(每个修复单独撤掉,测试必须变红):
| 撤掉的修复 | 变红的测试 |
|---|---|
分片数上限改回 target |
test_chunk_layout_exceeds_target、两条差分测试 |
| worker 拆不动就下班 | test_no_worker_exits_while_work_remains、test_workers_wait_for_new_chunks_instead_of_idling_out |
| 最小可拆大小固定(不自适应) | test_chunk_layout_exceeds_target、两条差分测试 |
| 「找活干」时占住连接名额 | 两条差分测试 |
v2.2.1
修剪贴板监控——macOS 和 Windows 上它从来就没工作过。用户 2026-10-28
实测发现的:两个平台都复现。
macOS:AppleScript 写错了,读取每轮都失败
代码写的是 osascript -e "pbpaste"。但 pbpaste 是 shell 命令,
不是 AppleScript 命令——AppleScript 解释器把它当成一个变量名,报
0:7: execution error: 变量"pbpaste"没有定义。 (-2753)
退出码非 0 → 读取函数返回 (None, -1) → 每一轮都失败。也就是说
「剪贴板链接自动识别」这个功能从上线到现在一次都没工作过。
改成直接调 /usr/bin/pbpaste:输出纯文本,剪贴板里是文件时输出空、
退出码 0(不会误报),而且不用每 0.5 秒起一个 AppleScript 解释器。
osascript -e "the clipboard" 留作兜底(那是 AppleScript 里读剪贴板的
正确写法,the clipboard 是 Standard Additions 的属性)。
Windows:句柄被截断,读出来是空的
GetClipboardData 返回的是 HANDLE(64 位)。代码没声明 restype,
而 ctypes 在没声明时默认按 c_int 返回——64 位 Windows 上句柄被截成
32 位。低 32 位最高位是 1 的时候(现代 Windows 的句柄值经常落在
这个区间),ctypes.c_void_p(handle) 会把这个负数补成
0xFFFFFFFFxxxxxxxx 的错指针,GlobalLock 拿不到数据 → 读出来是空的。
现在给所有 Win32 调用都声明了 argtypes / restype,
GetClipboardData 和 GlobalLock 的 restype 是 wintypes.HANDLE。
开关打开后线程压根没起
监听线程之前是「启动那一刻设置已经开着才起」。于是:
- 用户从设置页把开关打开 → 设置写对了,线程没跑 → 没反应
- 用户从 Windows 托盘把开关打开 → 同上,而且托盘那条路连
start()都不调
现在线程无条件常驻,「开不开」由它每轮自己读设置决定
(_capture_enabled 本来就是这么设计的,只是从来没人保证线程在跑)。
任何入口改开关,下一轮就生效。
三个入口统一走一个出口
菜单栏、托盘、设置页之前各写各的设置键——写的是同一个键倒没错,但
只有设置页那条会推 settings_updated,菜单栏和托盘勾完之后,
设置页上的开关还是老样子,用户以为没生效。现在三个入口都走
GuiBridge.set_clipboard_monitor():写设置 + 推事件 + 状态一致。
读失败不再静默
之前读失败只有一条 debug 级日志,而 Windows 用户默认日志级别是
warning——那条根本看不到,「剪贴板监控用不了」在用户那边是一个完全
无从查起的问题。现在失败是 warning 级(30 秒节流,不刷屏)。
设置页补了一条说明
「剪贴板链接自动识别」旁边加了一句:只有扩展名能识别成文件类型
(zip / dmg / mp4 …)的链接才会提示,聊天里的普通链接不会打扰你。
这是 Swift v1.0.7 起就有的过滤规则,但从来没在界面上说过。
v2.2.0
浏览器下载捕获——Chrome / Edge / Firefox / Safari 四个浏览器的下载
可以交给本程序接管。
之前这套东西其实写好了(契约 §4 那一整套:协议、原生宿主、回环通道、
四个扩展目录都在仓库里),但用户没法用:扩展只躺在源码仓库里,
应用里没有任何地方告诉用户「扩展在哪、装没装、token 是多少」——
连 Safari 那份连配对入口都没有。这一版把缺的那一段补齐。
设置页新增「浏览器接管」板块
打开「设置 → 浏览器接管」,能看到:
- 配对 token:可复制、可重新生成(重新生成 = 旧 token 立刻作废,
拿着旧 token 的扩展再也派不了单) - 每个浏览器的扩展目录:点「打开扩展目录」直接在访达/资源管理器里打开
- 每个浏览器装没装:应用自己扫扩展目录、查
pluginkit得到的真实状态 - 每个浏览器的安装入口:点「打开扩展管理页」直接跳到浏览器的扩展页
扩展文件现在跟着应用一起打包(macOS 在 Contents/Resources/extensions/,
Windows 在 onefile 的 sys._MEIPASS/extensions/),不用再去源码仓库里找。
Safari:走官方的「未签名的临时扩展」路径
本项目没有 Apple Developer Program 开发者签名,所以 Safari 扩展
不签名,走 Apple 官方给未签名扩展留的路子(macOS 17 / Safari 17 起):
设置 → 高级 → 「显示网页开发者功能」→ 设置 → 开发者 → 「允许未签名的扩展」
→ 设置 → 开发者 → 「添加临时扩展…」→ 选扩展目录 → 设置 → 扩展里勾选启用。
不需要 Xcode,不需要开发者账号。
平台的硬限制如实标在界面和文档里:退出 Safari 会重置「允许未签名的扩展」,
临时扩展最长 24 小时。Safari 也只能手动派单——它没有
downloads.onDeterminingFilename 那种「下载开始前拦一道」的钩子,
硬做只能等下载开始后再取消,那时候文件已经在路上,用户会丢文件。
Safari 还必须手动粘一次 token:Safari 没有 Chrome 那种原生消息宿主,
只能走回环 HTTP 通道——而那个端口带着 Access-Control-Allow-Origin: *,
不能在上面开 /pair(那等于把 token 贴给任何网页,等于打开
「谁都能往下载器塞任务」的门)。扩展的选项页和弹窗里都加了 token 输入框。
Chrome / Edge:把扩展 ID 钉死
宿主清单里的 allowed_origins 必须跟扩展的 origin 精确一致,否则
Chrome 直接拒绝拉起宿主。而「加载已解压的扩展程序」装出来的扩展,
ID 默认按目录路径算哈希——换个机器、换个目录就变了,清单永远对不上。
所以 Chrome / Edge 的 manifest 里写死了一把公钥(key 字段),扩展 ID
由公钥算出来,恒定为 bangcjehjdodcpfekmnkfmbeffcclpih(Chrome 和 Edge 同一个)。
Firefox 不需要这一步:它的扩展 ID 是 manifest 里
browser_specific_settings.gecko.id 自己定的。
升级扩展时不要换这把公钥——一换 ID 就变了,所有装好的宿主清单全部失效。
有一条测试专门盯着「manifest 里的 key 算出来的 ID」和「代码里写死的 ID」
是否一致。
其他
- Safari 扩展补了选项页(原来只有弹窗),版本号四个扩展统一到 2.2.0
- 扩展 manifest 加了
options_ui,Chrome/Firefox 也能从扩展详情页进选项页
v2.1.3
修一个真正的架构 bug(下载任务根本跑不起来),再加上自动分类、全局 UA,
以及去掉手动分片数。
下载一直卡住、0 字节 —— 根因是事件循环被抹掉了
上一版把「卡在 0 KB/s」当成网络问题修了一轮(探测超时、重试预算)。
这次拿到进程内的 Python 栈才看清:下载任务压根没跑起来。
GuiBridge.set_window() 里有这么一段:从当前线程猜一个正在跑的
asyncio 循环,猜不到就把 _loop 置成 None。而窗口是在主线程里建的,
主线程上根本没有 asyncio 循环(引擎的循环在 sdm-engine 线程里)——
于是 set_window 把 set_loop() 刚设好的引擎循环抹成了 None。
_loop 是 None 之后,桥接层所有调用退化成 asyncio.run(coro):
一次性循环。engine.start() 在这个一次性循环里把下载任务
create_task 出去,asyncio.run 一返回就拆循环,任务在跑第一行之前
就被取消。结果就是:任务躺在列表里永远不动、0 字节、连一个 socket
都没建过(lsof 上看不到任何对外连接)。暂停/继续/取消也都打在错误的
循环上,所以怎么点都没反应。添加框里的「解析」能成功,是因为它自己
开循环同步取结果,不依赖引擎循环。
修法:
set_window()不再碰_loop——引擎循环只有一个来源:set_loop()main()里set_loop()挪到set_window()之后- 桥接层加了兜底:真的没设上循环时,自己起一个常驻的后台循环,
绝不再退化成一次性的asyncio.run - 连接池等待加了超时上限(
pool=None的意思是「等连接池空位时永不
超时」,池满了就永久挂起)
自动分类
- 分类下拉第一个选项是「自动」并且默认选中。早先前端把第一个选项
(视频)当成分类发给后端,后端再覆盖掉引擎按 URL 猜出来的正确分类
——用户加一个 .dmg,列表里却归在「视频」。现在「自动」传的是 null,
由引擎判断 - 按服务器给的文件名判分类:URL 常常是无扩展名的 CDN 路径
(/download/12345),真正的文件名在Content-Disposition里。
探测成功后按真实文件名回填分类(.dmg → 程序),保存目录也跟着换 - 用户手动改过分类就不再自动改
全局 UA
- 设置页新增「全局 User-Agent」:留空 = 用程序内置的默认值,
随时可以改,改完立刻对后续所有下载生效 - 优先级:站点规则里的 UA → 全局 UA → 内置默认 UA
- 清空输入框是「恢复默认」,不是「发一个空的 UA」
去掉手动分片数
添加对话框里那个默认 8 的分片数输入框没有了。分片本来就是引擎动态
规划的(起始连接数按文件大小和实测速度自己长),挂一个数字框只会
让人以为那个数字决定实际分片数。
其它
- 探测失败的日志从 notice 提到 warning——用户把日志级别调到 warning
时,那恰恰是排查「卡在连接中」最关键的一条线索 - 引擎探测之后会按真实文件名修正任务的分类(用户没手动选过时)
v2.1.2
修两件事:下载一直卡在 0 KB/s,以及下载列表的界面错位。
「0 KB/s」那条查出来是三件事叠在一起:探测探了两次、探测用的超时是
分片那个 60 秒的看门狗、重试预算只算退避不算请求本身的时间。三条
一起,服务器慢的时候就是几分钟的静默——界面全程显示 0 KB/s,
还没有报错。界面错位那条更严重:列表的 grid 模板压根没生效。
界面
- 下载列表的列布局修好了:表头和数据行用的是同一套 grid 模板
(--row-cols,名字列是1fr),但这个变量被写在了列表容器上,
而它里面的var(--w-*)只定义在行和表头上——CSS 自定义属性是在
声明它的那个元素上做替换的,容器上找不到--w-name,整个模板
就成了无效值,grid-template-columns退回none,所有列竖着
叠起来(实测一行 281px 高,八个格子竖排)。现在模板写在用它的
元素上,列宽和窄屏自适应一起恢复 - 表头和数据行不再差 36px:列选择器按钮原来跟表头并排(flex),
把表头挤窄了 36px,名字之后的每一列都整体错开。现在按钮浮在表头
右端,表头拿回满宽 - 数字列右对齐:表头的「大小 / 速度 / 剩余时间」是右对齐的,行里
的数字却是左对齐的,标签和数字永远对不上 - 连接阶段显示「连接中」:从任务开跑到收到第一个字节之间不再显示
「0 KB/s」——那段时间确实一个字节都没拿到,但「还没开始」和「卡死
了」是两回事 - 顺手修掉 i18n 里 7 处乱码文案(「SHA-256:(0)」这类被二次编码
过的字符串)
引擎
- 探测不再探两次:引擎探一次拿真实文件名和总长度,下载流程又探
一次——两个 RTT 的纯等待。现在探一次的结果直接交给流程层 - 探测有自己的短超时:探测以前借用分片的 60 秒 read 超时(那是给
慢连接用的空闲看门狗),服务器不回响应头时一次探测就静默 60 秒,
重试几轮就是好几分钟。现在探测用 15 秒的独立超时 - 重试预算真的约束总时间:预算以前只统计退避的 sleep,不统计请求
本身耗掉的时间——服务器不响应时预算永远花不出去。现在请求耗时也
计入预算,最长二十几秒就会给出明确的失败 - 探测失败给一句人话:以前界面上显示的是
ReadTimeout这种英文类名,
现在是「连接服务器超时 / 连不上服务器 / 网络错误」
验证
- 引擎侧新增
tests/test_connect_phase.py(探测预算、不重复探测、连接
态生命周期),前端新增tests/test_connecting_js.mjs和
tests/test_column_alignment_js.mjs - 列对齐用无头浏览器量像素验的(Chromium + WebKit 都跑过):修复
前一行 281px 高、八格竖排;修复后行高 56px,表头和数据行的 grid
轨道完全一致
已知限制
- 仍不支持 BT / 磁力
- 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.1
修三个用户实际撞上的问题:改设置后页面总跳回顶部、开启开机自启会立刻多出一个进程、
解析链接一直在报「所有重试都失败」。顺带修好进度条最后一段经常少报一截的问题。
界面
- 改设置不再跳回页面顶部:改任何设置都会重画整个设置页,而重画会把滚动位置一起
冲掉——用户改个设置就被弹回顶部。现在重画前记下位置、重画后放回去 - 任务被删掉导致列表重画时,设置页的滚动位置同样不再丢
macOS
- 开启开机自启不再立刻多出一个 SDM 进程:之前写完 plist 还顺手跑了一次
launchctl load,而 load 不只是「注册」,它当场就把任务跑起来了。自启的意思是
「下次登录时启动」,不是「现在启动」。现在只写 plist,登录时 launchd 自己会扫
引擎
- 磁力链接这类地址立刻失败并说清楚:磁力链接(
magnet:开头)以前会被当成网络
错误重试 5 次,最后报一句「所有重试都失败」——跟真实原因完全对不上。现在直接告诉你
「这是磁力链接(BT),SDM 只下载 HTTP(S) 的普通文件,不支持 BT/磁力」 - 不可重试的失败不再重试:404(文件不存在)、416(服务器不认这个 Range)这类重试
多少次结果都一样,现在一次就报错,不再白等 7.5 秒 - 两处错误分类对齐:新建任务对话框和任务列表现在用同一份分类。磁力链接在两边都
归到「客户端错误」,不再在任务列表里显示成「网络连接中断,将自动重试」还标着可
重试;404 归到「文件不存在」;429(被限流)归到「可重试」 - 进度条最后一段不再少报:下载快结束时进度会停在 98.97% 再也不动(2 MB 的文件
最终报成 2031616,正好少一个 64 KB 块)。根因是进度合并窗口到期时发的是上一个
窗口攒的旧值,把最后那次上报直接抹掉了 - 续传时进度不再倒退一次:续传的汇总基数没算盘上已有的字节,第一次上报会比实际
少,进度条会往回跳一下,紧接着算出来的速度也是个巨大的假值
已知限制
- 仍不支持 BT / 磁力...
v2.2.1
v2.2.1
修剪贴板监控——macOS 和 Windows 上它从来就没工作过。用户 2026-10-28
实测发现的:两个平台都复现。
macOS:AppleScript 写错了,读取每轮都失败
代码写的是 osascript -e "pbpaste"。但 pbpaste 是 shell 命令,
不是 AppleScript 命令——AppleScript 解释器把它当成一个变量名,报
0:7: execution error: 变量"pbpaste"没有定义。 (-2753)
退出码非 0 → 读取函数返回 (None, -1) → 每一轮都失败。也就是说
「剪贴板链接自动识别」这个功能从上线到现在一次都没工作过。
改成直接调 /usr/bin/pbpaste:输出纯文本,剪贴板里是文件时输出空、
退出码 0(不会误报),而且不用每 0.5 秒起一个 AppleScript 解释器。
osascript -e "the clipboard" 留作兜底(那是 AppleScript 里读剪贴板的
正确写法,the clipboard 是 Standard Additions 的属性)。
Windows:句柄被截断,读出来是空的
GetClipboardData 返回的是 HANDLE(64 位)。代码没声明 restype,
而 ctypes 在没声明时默认按 c_int 返回——64 位 Windows 上句柄被截成
32 位。低 32 位最高位是 1 的时候(现代 Windows 的句柄值经常落在
这个区间),ctypes.c_void_p(handle) 会把这个负数补成
0xFFFFFFFFxxxxxxxx 的错指针,GlobalLock 拿不到数据 → 读出来是空的。
现在给所有 Win32 调用都声明了 argtypes / restype,
GetClipboardData 和 GlobalLock 的 restype 是 wintypes.HANDLE。
开关打开后线程压根没起
监听线程之前是「启动那一刻设置已经开着才起」。于是:
- 用户从设置页把开关打开 → 设置写对了,线程没跑 → 没反应
- 用户从 Windows 托盘把开关打开 → 同上,而且托盘那条路连
start()都不调
现在线程无条件常驻,「开不开」由它每轮自己读设置决定
(_capture_enabled 本来就是这么设计的,只是从来没人保证线程在跑)。
任何入口改开关,下一轮就生效。
三个入口统一走一个出口
菜单栏、托盘、设置页之前各写各的设置键——写的是同一个键倒没错,但
只有设置页那条会推 settings_updated,菜单栏和托盘勾完之后,
设置页上的开关还是老样子,用户以为没生效。现在三个入口都走
GuiBridge.set_clipboard_monitor():写设置 + 推事件 + 状态一致。
读失败不再静默
之前读失败只有一条 debug 级日志,而 Windows 用户默认日志级别是
warning——那条根本看不到,「剪贴板监控用不了」在用户那边是一个完全
无从查起的问题。现在失败是 warning 级(30 秒节流,不刷屏)。
设置页补了一条说明
「剪贴板链接自动识别」旁边加了一句:只有扩展名能识别成文件类型
(zip / dmg / mp4 …)的链接才会提示,聊天里的普通链接不会打扰你。
这是 Swift v1.0.7 起就有的过滤规则,但从来没在界面上说过。
v2.2.0
浏览器下载捕获——Chrome / Edge / Firefox / Safari 四个浏览器的下载
可以交给本程序接管。
之前这套东西其实写好了(契约 §4 那一整套:协议、原生宿主、回环通道、
四个扩展目录都在仓库里),但用户没法用:扩展只躺在源码仓库里,
应用里没有任何地方告诉用户「扩展在哪、装没装、token 是多少」——
连 Safari 那份连配对入口都没有。这一版把缺的那一段补齐。
设置页新增「浏览器接管」板块
打开「设置 → 浏览器接管」,能看到:
- 配对 token:可复制、可重新生成(重新生成 = 旧 token 立刻作废,
拿着旧 token 的扩展再也派不了单) - 每个浏览器的扩展目录:点「打开扩展目录」直接在访达/资源管理器里打开
- 每个浏览器装没装:应用自己扫扩展目录、查
pluginkit得到的真实状态 - 每个浏览器的安装入口:点「打开扩展管理页」直接跳到浏览器的扩展页
扩展文件现在跟着应用一起打包(macOS 在 Contents/Resources/extensions/,
Windows 在 onefile 的 sys._MEIPASS/extensions/),不用再去源码仓库里找。
Safari:走官方的「未签名的临时扩展」路径
本项目没有 Apple Developer Program 开发者签名,所以 Safari 扩展
不签名,走 Apple 官方给未签名扩展留的路子(macOS 17 / Safari 17 起):
设置 → 高级 → 「显示网页开发者功能」→ 设置 → 开发者 → 「允许未签名的扩展」
→ 设置 → 开发者 → 「添加临时扩展…」→ 选扩展目录 → 设置 → 扩展里勾选启用。
不需要 Xcode,不需要开发者账号。
平台的硬限制如实标在界面和文档里:退出 Safari 会重置「允许未签名的扩展」,
临时扩展最长 24 小时。Safari 也只能手动派单——它没有
downloads.onDeterminingFilename 那种「下载开始前拦一道」的钩子,
硬做只能等下载开始后再取消,那时候文件已经在路上,用户会丢文件。
Safari 还必须手动粘一次 token:Safari 没有 Chrome 那种原生消息宿主,
只能走回环 HTTP 通道——而那个端口带着 Access-Control-Allow-Origin: *,
不能在上面开 /pair(那等于把 token 贴给任何网页,等于打开
「谁都能往下载器塞任务」的门)。扩展的选项页和弹窗里都加了 token 输入框。
Chrome / Edge:把扩展 ID 钉死
宿主清单里的 allowed_origins 必须跟扩展的 origin 精确一致,否则
Chrome 直接拒绝拉起宿主。而「加载已解压的扩展程序」装出来的扩展,
ID 默认按目录路径算哈希——换个机器、换个目录就变了,清单永远对不上。
所以 Chrome / Edge 的 manifest 里写死了一把公钥(key 字段),扩展 ID
由公钥算出来,恒定为 bangcjehjdodcpfekmnkfmbeffcclpih(Chrome 和 Edge 同一个)。
Firefox 不需要这一步:它的扩展 ID 是 manifest 里
browser_specific_settings.gecko.id 自己定的。
升级扩展时不要换这把公钥——一换 ID 就变了,所有装好的宿主清单全部失效。
有一条测试专门盯着「manifest 里的 key 算出来的 ID」和「代码里写死的 ID」
是否一致。
其他
- Safari 扩展补了选项页(原来只有弹窗),版本号四个扩展统一到 2.2.0
- 扩展 manifest 加了
options_ui,Chrome/Firefox 也能从扩展详情页进选项页
v2.1.3
修一个真正的架构 bug(下载任务根本跑不起来),再加上自动分类、全局 UA,
以及去掉手动分片数。
下载一直卡住、0 字节 —— 根因是事件循环被抹掉了
上一版把「卡在 0 KB/s」当成网络问题修了一轮(探测超时、重试预算)。
这次拿到进程内的 Python 栈才看清:下载任务压根没跑起来。
GuiBridge.set_window() 里有这么一段:从当前线程猜一个正在跑的
asyncio 循环,猜不到就把 _loop 置成 None。而窗口是在主线程里建的,
主线程上根本没有 asyncio 循环(引擎的循环在 sdm-engine 线程里)——
于是 set_window 把 set_loop() 刚设好的引擎循环抹成了 None。
_loop 是 None 之后,桥接层所有调用退化成 asyncio.run(coro):
一次性循环。engine.start() 在这个一次性循环里把下载任务
create_task 出去,asyncio.run 一返回就拆循环,任务在跑第一行之前
就被取消。结果就是:任务躺在列表里永远不动、0 字节、连一个 socket
都没建过(lsof 上看不到任何对外连接)。暂停/继续/取消也都打在错误的
循环上,所以怎么点都没反应。添加框里的「解析」能成功,是因为它自己
开循环同步取结果,不依赖引擎循环。
修法:
set_window()不再碰_loop——引擎循环只有一个来源:set_loop()main()里set_loop()挪到set_window()之后- 桥接层加了兜底:真的没设上循环时,自己起一个常驻的后台循环,
绝不再退化成一次性的asyncio.run - 连接池等待加了超时上限(
pool=None的意思是「等连接池空位时永不
超时」,池满了就永久挂起)
自动分类
- 分类下拉第一个选项是「自动」并且默认选中。早先前端把第一个选项
(视频)当成分类发给后端,后端再覆盖掉引擎按 URL 猜出来的正确分类
——用户加一个 .dmg,列表里却归在「视频」。现在「自动」传的是 null,
由引擎判断 - 按服务器给的文件名判分类:URL 常常是无扩展名的 CDN 路径
(/download/12345),真正的文件名在Content-Disposition里。
探测成功后按真实文件名回填分类(.dmg → 程序),保存目录也跟着换 - 用户手动改过分类就不再自动改
全局 UA
- 设置页新增「全局 User-Agent」:留空 = 用程序内置的默认值,
随时可以改,改完立刻对后续所有下载生效 - 优先级:站点规则里的 UA → 全局 UA → 内置默认 UA
- 清空输入框是「恢复默认」,不是「发一个空的 UA」
去掉手动分片数
添加对话框里那个默认 8 的分片数输入框没有了。分片本来就是引擎动态
规划的(起始连接数按文件大小和实测速度自己长),挂一个数字框只会
让人以为那个数字决定实际分片数。
其它
- 探测失败的日志从 notice 提到 warning——用户把日志级别调到 warning
时,那恰恰是排查「卡在连接中」最关键的一条线索 - 引擎探测之后会按真实文件名修正任务的分类(用户没手动选过时)
v2.1.2
修两件事:下载一直卡在 0 KB/s,以及下载列表的界面错位。
「0 KB/s」那条查出来是三件事叠在一起:探测探了两次、探测用的超时是
分片那个 60 秒的看门狗、重试预算只算退避不算请求本身的时间。三条
一起,服务器慢的时候就是几分钟的静默——界面全程显示 0 KB/s,
还没有报错。界面错位那条更严重:列表的 grid 模板压根没生效。
界面
- 下载列表的列布局修好了:表头和数据行用的是同一套 grid 模板
(--row-cols,名字列是1fr),但这个变量被写在了列表容器上,
而它里面的var(--w-*)只定义在行和表头上——CSS 自定义属性是在
声明它的那个元素上做替换的,容器上找不到--w-name,整个模板
就成了无效值,grid-template-columns退回none,所有列竖着
叠起来(实测一行 281px 高,八个格子竖排)。现在模板写在用它的
元素上,列宽和窄屏自适应一起恢复 - 表头和数据行不再差 36px:列选择器按钮原来跟表头并排(flex),
把表头挤窄了 36px,名字之后的每一列都整体错开。现在按钮浮在表头
右端,表头拿回满宽 - 数字列右对齐:表头的「大小 / 速度 / 剩余时间」是右对齐的,行里
的数字却是左对齐的,标签和数字永远对不上 - 连接阶段显示「连接中」:从任务开跑到收到第一个字节之间不再显示
「0 KB/s」——那段时间确实一个字节都没拿到,但「还没开始」和「卡死
了」是两回事 - 顺手修掉 i18n 里 7 处乱码文案(「SHA-256:(0)」这类被二次编码
过的字符串)
引擎
- 探测不再探两次:引擎探一次拿真实文件名和总长度,下载流程又探
一次——两个 RTT 的纯等待。现在探一次的结果直接交给流程层 - 探测有自己的短超时:探测以前借用分片的 60 秒 read 超时(那是给
慢连接用的空闲看门狗),服务器不回响应头时一次探测就静默 60 秒,
重试几轮就是好几分钟。现在探测用 15 秒的独立超时 - 重试预算真的约束总时间:预算以前只统计退避的 sleep,不统计请求
本身耗掉的时间——服务器不响应时预算永远花不出去。现在请求耗时也
计入预算,最长二十几秒就会给出明确的失败 - 探测失败给一句人话:以前界面上显示的是
ReadTimeout这种英文类名,
现在是「连接服务器超时 / 连不上服务器 / 网络错误」
验证
- 引擎侧新增
tests/test_connect_phase.py(探测预算、不重复探测、连接
态生命周期),前端新增tests/test_connecting_js.mjs和
tests/test_column_alignment_js.mjs - 列对齐用无头浏览器量像素验的(Chromium + WebKit 都跑过):修复
前一行 281px 高、八格竖排;修复后行高 56px,表头和数据行的 grid
轨道完全一致
已知限制
- 仍不支持 BT / 磁力
- 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.1
修三个用户实际撞上的问题:改设置后页面总跳回顶部、开启开机自启会立刻多出一个进程、
解析链接一直在报「所有重试都失败」。顺带修好进度条最后一段经常少报一截的问题。
界面
- 改设置不再跳回页面顶部:改任何设置都会重画整个设置页,而重画会把滚动位置一起
冲掉——用户改个设置就被弹回顶部。现在重画前记下位置、重画后放回去 - 任务被删掉导致列表重画时,设置页的滚动位置同样不再丢
macOS
- 开启开机自启不再立刻多出一个 SDM 进程:之前写完 plist 还顺手跑了一次
launchctl load,而 load 不只是「注册」,它当场就把任务跑起来了。自启的意思是
「下次登录时启动」,不是「现在启动」。现在只写 plist,登录时 launchd 自己会扫
引擎
- 磁力链接这类地址立刻失败并说清楚:磁力链接(
magnet:开头)以前会被当成网络
错误重试 5 次,最后报一句「所有重试都失败」——跟真实原因完全对不上。现在直接告诉你
「这是磁力链接(BT),SDM 只下载 HTTP(S) 的普通文件,不支持 BT/磁力」 - 不可重试的失败不再重试:404(文件不存在)、416(服务器不认这个 Range)这类重试
多少次结果都一样,现在一次就报错,不再白等 7.5 秒 - 两处错误分类对齐:新建任务对话框和任务列表现在用同一份分类。磁力链接在两边都
归到「客户端错误」,不再在任务列表里显示成「网络连接中断,将自动重试」还标着可
重试;404 归到「文件不存在」;429(被限流)归到「可重试」 - 进度条最后一段不再少报:下载快结束时进度会停在 98.97% 再也不动(2 MB 的文件
最终报成 2031616,正好少一个 64 KB 块)。根因是进度合并窗口到期时发的是上一个
窗口攒的旧值,把最后那次上报直接抹掉了 - 续传时进度不再倒退一次:续传的汇总基数没算盘上已有的字节,第一次上报会比实际
少,进度条会往回跳一下,紧接着算出来的速度也是个巨大的假值
已知限制
- 仍不支持 BT / 磁力(要做磁力得整个塞一个 BT 客户端进来,不在当前范围内);现在遇到
这类地址会立刻说清楚原因,而不是重试到天荒地老 - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.0
这一版按 DeepSeek 规格做了整轮补齐,重点在引擎和错误处理——这两块是真正
的壁垒,其余是体验放大器。
引擎
- 动态分段:分片布局在下载过程中会长大,不再是开跑前一次性切死。从 2 条
连接起步,有实打实的进度才逐级放开到设定上限;单个分片跑到一半会被拆成两半,
拆出来的另一半另开连接继续拉 - 无 Range 自动回退:服务器对 Range 请求返回 200 而不是 206 时,自动退化成
单流下载,不再把整份文件当成分片数据写坏 - 续传校验:会话文件里记了 ETag / Last-Modified / Content-Length,资源变了
就不再接着旧进度写,任务转成「资源已变更」而不是悄悄写出一个坏文件 - 会话落盘:分片布局写成
<文件名>.sdm.session.json,崩溃或被杀之后能按原
布局接着下 - 进度报增量:文件级进度是所有分片增量之和。修好了进度虚高 2.5 倍的 bug
(2 MB 的文件一度报成 5 MB) - 代理:支持 http / https 原生代理,SOCKS4 / SOCKS5 走
httpx-socks - 校验和:可选 md5 / sha1 / sha256,完成后自动算出校验值
- 完成后行为:打开文件、打开文件夹、跑脚本、休眠,可按任务设置
- 磁盘空间预检:开跑前检查剩余空间,不够就直接失败,不下到一半才发现
- 名字冲突:默认保留两者(
foo (1).zip),覆盖要走「移到废纸篓」,不会
静默删掉用户已有文件 - 任务队列:同时活跃的任务数有上限(默认 5),超出的排队,可置顶置底
- 重试与改选项:失败任务可重试,代理 / 校验算法 / 完成后行为可以中途改
错误处理
- 12 类结构化错误分类:网络、磁盘、权限、空间不足、认证、链接过期、服务器、
限流、协议、不支持、用户取消、未知 - 每类错误都带能不能自动重试和用户该做什么的中文说明
- HTTP 状态码自动归类,403 / 410 / 451 归到「链接失效」,任务转「等待新链接」
- 尊重
Retry-After,重试用指数退避 + 抖动,最多 5 次 - 错误详情可以复制出来,粘到反馈里能看懂
浏览器接管
- Native Messaging 宿主:Chrome / Edge / Firefox / Safari 扩展通过原生消息
把下载派给应用,自动带上页面的 Cookie、User-Agent、Referer - 配对令牌:扩展第一次连接要跟应用配对,令牌存在本地,回环 HTTP 通道强制
校验 - Chrome / Edge 扩展重写:取消浏览器自带的下载,接管文件名、按分类分目录
- Firefox 修好:以前把文件名当 URL 发出去,压根下不动
- Safari 改成纯手动:右键菜单 + 工具栏按钮,不做自动拦截
界面
- 底部状态栏:全局速度、限速、活跃任务数
- 分类列 + 列显隐选择器
- 新建任务对话框重写:多行 URL、保存目录、分类、代理、请求头键值行、分块数、
开始时机(立即 / 排队 / 定时),带解析状态和重新解析 - 详情面板:分片可视化 + 最近 60 秒的速度曲线
- 浅色 / 深色主题切换,跟随系统
- 设置里补齐 7 个新键(并发任务数、主题、代理、配对令牌、初始连接数、分片上限等)
macOS / Windows 系统集成
- 菜单栏状态行:实时显示「下载中 N 个 · 速度」,1.5 秒刷一次;带暂停全部、
继续全部、剪贴板监控勾选 - Dock 图标显示整体下载进度
- AppleScript 词典:可在 Script Editor 里
add download、pause all downloads、
resume all downloads、get download list - Windows 托盘图标 + 与 macOS 一致的菜单
- Windows URL Scheme 注册表项
打包
- 新增
sdmd-native-host产物,浏览器按清单拉起它 - macOS 首次启动自动装原生消息宿主清单,Windows 解包后自动装
- AppleScript 词典打进
.app,Info.plist里写OSAScriptingDefinition
已知限制
- Safari 只做手动接管,不做自动拦截
- 不支持 BT / 磁力
- 没有 App Intents 原生集成
- SOCKS 代理需要额外装
httpx-socks - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.0.6
- 下载列表可以按文件名、大小、速度、状态排序,未知大小的任务排在最后
- 速度显示改成平滑曲线,不再忽快忽慢;任务结束或暂停时速度归零
- 下载完成后自动写进历史记录
- 日志每天自动清理一次,改动日志设置时也会立即清理
- 设置里可以直接打开 macOS 的 Console.app 看日志
- 下载完成和失败时发系统通知;应用在前台时也弹横幅;点通知会把主窗口拉到前面
- 站点规则对探测请求也生效,需要 Referer 或特定 UA 的站点不再一启动就失败
- 命令行和后台守护支持用环境变量指定 socket 路径
- 后台守护必须由系统服务启动,手动运行会给出提示而不是挂在前台
- 剪贴板候选弹窗可以随时停止监听;已经在下载队列里的链接不再重复提示
- 关闭自动分类后,所有下载落进下载目录根目录
- 关掉主窗口后应用继续在菜单栏待着,可从菜单栏唤回
- 关闭窗口时按设置隐藏 Dock 图标,从菜单栏唤回时恢复
- 启动时自动同步开机自启状态
- 带宽限制输入框的单位改成 KB/s,之前填的数值实际是按字节算的
- 支持 `superdo...
v2.2.0
v2.2.0
浏览器下载捕获——Chrome / Edge / Firefox / Safari 四个浏览器的下载
可以交给本程序接管。
之前这套东西其实写好了(契约 §4 那一整套:协议、原生宿主、回环通道、
四个扩展目录都在仓库里),但用户没法用:扩展只躺在源码仓库里,
应用里没有任何地方告诉用户「扩展在哪、装没装、token 是多少」——
连 Safari 那份连配对入口都没有。这一版把缺的那一段补齐。
设置页新增「浏览器接管」板块
打开「设置 → 浏览器接管」,能看到:
- 配对 token:可复制、可重新生成(重新生成 = 旧 token 立刻作废,
拿着旧 token 的扩展再也派不了单) - 每个浏览器的扩展目录:点「打开扩展目录」直接在访达/资源管理器里打开
- 每个浏览器装没装:应用自己扫扩展目录、查
pluginkit得到的真实状态 - 每个浏览器的安装入口:点「打开扩展管理页」直接跳到浏览器的扩展页
扩展文件现在跟着应用一起打包(macOS 在 Contents/Resources/extensions/,
Windows 在 onefile 的 sys._MEIPASS/extensions/),不用再去源码仓库里找。
Safari:走官方的「未签名的临时扩展」路径
本项目没有 Apple Developer Program 开发者签名,所以 Safari 扩展
不签名,走 Apple 官方给未签名扩展留的路子(macOS 17 / Safari 17 起):
设置 → 高级 → 「显示网页开发者功能」→ 设置 → 开发者 → 「允许未签名的扩展」
→ 设置 → 开发者 → 「添加临时扩展…」→ 选扩展目录 → 设置 → 扩展里勾选启用。
不需要 Xcode,不需要开发者账号。
平台的硬限制如实标在界面和文档里:退出 Safari 会重置「允许未签名的扩展」,
临时扩展最长 24 小时。Safari 也只能手动派单——它没有
downloads.onDeterminingFilename 那种「下载开始前拦一道」的钩子,
硬做只能等下载开始后再取消,那时候文件已经在路上,用户会丢文件。
Safari 还必须手动粘一次 token:Safari 没有 Chrome 那种原生消息宿主,
只能走回环 HTTP 通道——而那个端口带着 Access-Control-Allow-Origin: *,
不能在上面开 /pair(那等于把 token 贴给任何网页,等于打开
「谁都能往下载器塞任务」的门)。扩展的选项页和弹窗里都加了 token 输入框。
Chrome / Edge:把扩展 ID 钉死
宿主清单里的 allowed_origins 必须跟扩展的 origin 精确一致,否则
Chrome 直接拒绝拉起宿主。而「加载已解压的扩展程序」装出来的扩展,
ID 默认按目录路径算哈希——换个机器、换个目录就变了,清单永远对不上。
所以 Chrome / Edge 的 manifest 里写死了一把公钥(key 字段),扩展 ID
由公钥算出来,恒定为 bangcjehjdodcpfekmnkfmbeffcclpih(Chrome 和 Edge 同一个)。
Firefox 不需要这一步:它的扩展 ID 是 manifest 里
browser_specific_settings.gecko.id 自己定的。
升级扩展时不要换这把公钥——一换 ID 就变了,所有装好的宿主清单全部失效。
有一条测试专门盯着「manifest 里的 key 算出来的 ID」和「代码里写死的 ID」
是否一致。
其他
- Safari 扩展补了选项页(原来只有弹窗),版本号四个扩展统一到 2.2.0
- 扩展 manifest 加了
options_ui,Chrome/Firefox 也能从扩展详情页进选项页
v2.1.3
修一个真正的架构 bug(下载任务根本跑不起来),再加上自动分类、全局 UA,
以及去掉手动分片数。
下载一直卡住、0 字节 —— 根因是事件循环被抹掉了
上一版把「卡在 0 KB/s」当成网络问题修了一轮(探测超时、重试预算)。
这次拿到进程内的 Python 栈才看清:下载任务压根没跑起来。
GuiBridge.set_window() 里有这么一段:从当前线程猜一个正在跑的
asyncio 循环,猜不到就把 _loop 置成 None。而窗口是在主线程里建的,
主线程上根本没有 asyncio 循环(引擎的循环在 sdm-engine 线程里)——
于是 set_window 把 set_loop() 刚设好的引擎循环抹成了 None。
_loop 是 None 之后,桥接层所有调用退化成 asyncio.run(coro):
一次性循环。engine.start() 在这个一次性循环里把下载任务
create_task 出去,asyncio.run 一返回就拆循环,任务在跑第一行之前
就被取消。结果就是:任务躺在列表里永远不动、0 字节、连一个 socket
都没建过(lsof 上看不到任何对外连接)。暂停/继续/取消也都打在错误的
循环上,所以怎么点都没反应。添加框里的「解析」能成功,是因为它自己
开循环同步取结果,不依赖引擎循环。
修法:
set_window()不再碰_loop——引擎循环只有一个来源:set_loop()main()里set_loop()挪到set_window()之后- 桥接层加了兜底:真的没设上循环时,自己起一个常驻的后台循环,
绝不再退化成一次性的asyncio.run - 连接池等待加了超时上限(
pool=None的意思是「等连接池空位时永不
超时」,池满了就永久挂起)
自动分类
- 分类下拉第一个选项是「自动」并且默认选中。早先前端把第一个选项
(视频)当成分类发给后端,后端再覆盖掉引擎按 URL 猜出来的正确分类
——用户加一个 .dmg,列表里却归在「视频」。现在「自动」传的是 null,
由引擎判断 - 按服务器给的文件名判分类:URL 常常是无扩展名的 CDN 路径
(/download/12345),真正的文件名在Content-Disposition里。
探测成功后按真实文件名回填分类(.dmg → 程序),保存目录也跟着换 - 用户手动改过分类就不再自动改
全局 UA
- 设置页新增「全局 User-Agent」:留空 = 用程序内置的默认值,
随时可以改,改完立刻对后续所有下载生效 - 优先级:站点规则里的 UA → 全局 UA → 内置默认 UA
- 清空输入框是「恢复默认」,不是「发一个空的 UA」
去掉手动分片数
添加对话框里那个默认 8 的分片数输入框没有了。分片本来就是引擎动态
规划的(起始连接数按文件大小和实测速度自己长),挂一个数字框只会
让人以为那个数字决定实际分片数。
其它
- 探测失败的日志从 notice 提到 warning——用户把日志级别调到 warning
时,那恰恰是排查「卡在连接中」最关键的一条线索 - 引擎探测之后会按真实文件名修正任务的分类(用户没手动选过时)
v2.1.2
修两件事:下载一直卡在 0 KB/s,以及下载列表的界面错位。
「0 KB/s」那条查出来是三件事叠在一起:探测探了两次、探测用的超时是
分片那个 60 秒的看门狗、重试预算只算退避不算请求本身的时间。三条
一起,服务器慢的时候就是几分钟的静默——界面全程显示 0 KB/s,
还没有报错。界面错位那条更严重:列表的 grid 模板压根没生效。
界面
- 下载列表的列布局修好了:表头和数据行用的是同一套 grid 模板
(--row-cols,名字列是1fr),但这个变量被写在了列表容器上,
而它里面的var(--w-*)只定义在行和表头上——CSS 自定义属性是在
声明它的那个元素上做替换的,容器上找不到--w-name,整个模板
就成了无效值,grid-template-columns退回none,所有列竖着
叠起来(实测一行 281px 高,八个格子竖排)。现在模板写在用它的
元素上,列宽和窄屏自适应一起恢复 - 表头和数据行不再差 36px:列选择器按钮原来跟表头并排(flex),
把表头挤窄了 36px,名字之后的每一列都整体错开。现在按钮浮在表头
右端,表头拿回满宽 - 数字列右对齐:表头的「大小 / 速度 / 剩余时间」是右对齐的,行里
的数字却是左对齐的,标签和数字永远对不上 - 连接阶段显示「连接中」:从任务开跑到收到第一个字节之间不再显示
「0 KB/s」——那段时间确实一个字节都没拿到,但「还没开始」和「卡死
了」是两回事 - 顺手修掉 i18n 里 7 处乱码文案(「SHA-256:(0)」这类被二次编码
过的字符串)
引擎
- 探测不再探两次:引擎探一次拿真实文件名和总长度,下载流程又探
一次——两个 RTT 的纯等待。现在探一次的结果直接交给流程层 - 探测有自己的短超时:探测以前借用分片的 60 秒 read 超时(那是给
慢连接用的空闲看门狗),服务器不回响应头时一次探测就静默 60 秒,
重试几轮就是好几分钟。现在探测用 15 秒的独立超时 - 重试预算真的约束总时间:预算以前只统计退避的 sleep,不统计请求
本身耗掉的时间——服务器不响应时预算永远花不出去。现在请求耗时也
计入预算,最长二十几秒就会给出明确的失败 - 探测失败给一句人话:以前界面上显示的是
ReadTimeout这种英文类名,
现在是「连接服务器超时 / 连不上服务器 / 网络错误」
验证
- 引擎侧新增
tests/test_connect_phase.py(探测预算、不重复探测、连接
态生命周期),前端新增tests/test_connecting_js.mjs和
tests/test_column_alignment_js.mjs - 列对齐用无头浏览器量像素验的(Chromium + WebKit 都跑过):修复
前一行 281px 高、八格竖排;修复后行高 56px,表头和数据行的 grid
轨道完全一致
已知限制
- 仍不支持 BT / 磁力
- 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.1
修三个用户实际撞上的问题:改设置后页面总跳回顶部、开启开机自启会立刻多出一个进程、
解析链接一直在报「所有重试都失败」。顺带修好进度条最后一段经常少报一截的问题。
界面
- 改设置不再跳回页面顶部:改任何设置都会重画整个设置页,而重画会把滚动位置一起
冲掉——用户改个设置就被弹回顶部。现在重画前记下位置、重画后放回去 - 任务被删掉导致列表重画时,设置页的滚动位置同样不再丢
macOS
- 开启开机自启不再立刻多出一个 SDM 进程:之前写完 plist 还顺手跑了一次
launchctl load,而 load 不只是「注册」,它当场就把任务跑起来了。自启的意思是
「下次登录时启动」,不是「现在启动」。现在只写 plist,登录时 launchd 自己会扫
引擎
- 磁力链接这类地址立刻失败并说清楚:磁力链接(
magnet:开头)以前会被当成网络
错误重试 5 次,最后报一句「所有重试都失败」——跟真实原因完全对不上。现在直接告诉你
「这是磁力链接(BT),SDM 只下载 HTTP(S) 的普通文件,不支持 BT/磁力」 - 不可重试的失败不再重试:404(文件不存在)、416(服务器不认这个 Range)这类重试
多少次结果都一样,现在一次就报错,不再白等 7.5 秒 - 两处错误分类对齐:新建任务对话框和任务列表现在用同一份分类。磁力链接在两边都
归到「客户端错误」,不再在任务列表里显示成「网络连接中断,将自动重试」还标着可
重试;404 归到「文件不存在」;429(被限流)归到「可重试」 - 进度条最后一段不再少报:下载快结束时进度会停在 98.97% 再也不动(2 MB 的文件
最终报成 2031616,正好少一个 64 KB 块)。根因是进度合并窗口到期时发的是上一个
窗口攒的旧值,把最后那次上报直接抹掉了 - 续传时进度不再倒退一次:续传的汇总基数没算盘上已有的字节,第一次上报会比实际
少,进度条会往回跳一下,紧接着算出来的速度也是个巨大的假值
已知限制
- 仍不支持 BT / 磁力(要做磁力得整个塞一个 BT 客户端进来,不在当前范围内);现在遇到
这类地址会立刻说清楚原因,而不是重试到天荒地老 - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.0
这一版按 DeepSeek 规格做了整轮补齐,重点在引擎和错误处理——这两块是真正
的壁垒,其余是体验放大器。
引擎
- 动态分段:分片布局在下载过程中会长大,不再是开跑前一次性切死。从 2 条
连接起步,有实打实的进度才逐级放开到设定上限;单个分片跑到一半会被拆成两半,
拆出来的另一半另开连接继续拉 - 无 Range 自动回退:服务器对 Range 请求返回 200 而不是 206 时,自动退化成
单流下载,不再把整份文件当成分片数据写坏 - 续传校验:会话文件里记了 ETag / Last-Modified / Content-Length,资源变了
就不再接着旧进度写,任务转成「资源已变更」而不是悄悄写出一个坏文件 - 会话落盘:分片布局写成
<文件名>.sdm.session.json,崩溃或被杀之后能按原
布局接着下 - 进度报增量:文件级进度是所有分片增量之和。修好了进度虚高 2.5 倍的 bug
(2 MB 的文件一度报成 5 MB) - 代理:支持 http / https 原生代理,SOCKS4 / SOCKS5 走
httpx-socks - 校验和:可选 md5 / sha1 / sha256,完成后自动算出校验值
- 完成后行为:打开文件、打开文件夹、跑脚本、休眠,可按任务设置
- 磁盘空间预检:开跑前检查剩余空间,不够就直接失败,不下到一半才发现
- 名字冲突:默认保留两者(
foo (1).zip),覆盖要走「移到废纸篓」,不会
静默删掉用户已有文件 - 任务队列:同时活跃的任务数有上限(默认 5),超出的排队,可置顶置底
- 重试与改选项:失败任务可重试,代理 / 校验算法 / 完成后行为可以中途改
错误处理
- 12 类结构化错误分类:网络、磁盘、权限、空间不足、认证、链接过期、服务器、
限流、协议、不支持、用户取消、未知 - 每类错误都带能不能自动重试和用户该做什么的中文说明
- HTTP 状态码自动归类,403 / 410 / 451 归到「链接失效」,任务转「等待新链接」
- 尊重
Retry-After,重试用指数退避 + 抖动,最多 5 次 - 错误详情可以复制出来,粘到反馈里能看懂
浏览器接管
- Native Messaging 宿主:Chrome / Edge / Firefox / Safari 扩展通过原生消息
把下载派给应用,自动带上页面的 Cookie、User-Agent、Referer - 配对令牌:扩展第一次连接要跟应用配对,令牌存在本地,回环 HTTP 通道强制
校验 - Chrome / Edge 扩展重写:取消浏览器自带的下载,接管文件名、按分类分目录
- Firefox 修好:以前把文件名当 URL 发出去,压根下不动
- Safari 改成纯手动:右键菜单 + 工具栏按钮,不做自动拦截
界面
- 底部状态栏:全局速度、限速、活跃任务数
- 分类列 + 列显隐选择器
- 新建任务对话框重写:多行 URL、保存目录、分类、代理、请求头键值行、分块数、
开始时机(立即 / 排队 / 定时),带解析状态和重新解析 - 详情面板:分片可视化 + 最近 60 秒的速度曲线
- 浅色 / 深色主题切换,跟随系统
- 设置里补齐 7 个新键(并发任务数、主题、代理、配对令牌、初始连接数、分片上限等)
macOS / Windows 系统集成
- 菜单栏状态行:实时显示「下载中 N 个 · 速度」,1.5 秒刷一次;带暂停全部、
继续全部、剪贴板监控勾选 - Dock 图标显示整体下载进度
- AppleScript 词典:可在 Script Editor 里
add download、pause all downloads、
resume all downloads、get download list - Windows 托盘图标 + 与 macOS 一致的菜单
- Windows URL Scheme 注册表项
打包
- 新增
sdmd-native-host产物,浏览器按清单拉起它 - macOS 首次启动自动装原生消息宿主清单,Windows 解包后自动装
- AppleScript 词典打进
.app,Info.plist里写OSAScriptingDefinition
已知限制
- Safari 只做手动接管,不做自动拦截
- 不支持 BT / 磁力
- 没有 App Intents 原生集成
- SOCKS 代理需要额外装
httpx-socks - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.0.6
- 下载列表可以按文件名、大小、速度、状态排序,未知大小的任务排在最后
- 速度显示改成平滑曲线,不再忽快忽慢;任务结束或暂停时速度归零
- 下载完成后自动写进历史记录
- 日志每天自动清理一次,改动日志设置时也会立即清理
- 设置里可以直接打开 macOS 的 Console.app 看日志
- 下载完成和失败时发系统通知;应用在前台时也弹横幅;点通知会把主窗口拉到前面
- 站点规则对探测请求也生效,需要 Referer 或特定 UA 的站点不再一启动就失败
- 命令行和后台守护支持用环境变量指定 socket 路径
- 后台守护必须由系统服务启动,手动运行会给出提示而不是挂在前台
- 剪贴板候选弹窗可以随时停止监听;已经在下载队列里的链接不再重复提示
- 关闭自动分类后,所有下载落进下载目录根目录
- 关掉主窗口后应用继续在菜单栏待着,可从菜单栏唤回
- 关闭窗口时按设置隐藏 Dock 图标,从菜单栏唤回时恢复
- 启动时自动同步开机自启状态
- 带宽限制输入框的单位改成 KB/s,之前填的数值实际是按字节算的
- 支持
superdownloadmanager://链接唤起下载
v2.0.5
- 修好分片计数:详情页的「已完成分片 / 总片数」以前一直是 0/0,现在正确显示
- 最后一个分片完成后状态不再停留在未完成
- 已完成的任务也能查看分片明细
- 修好 socket 路径过长时 daemon 起不来:路径超过系统上限自动改用短路径
- 打包时校验应用图标,没打进 bundle 直接让构建失败
v2.0.4
- 详情页分片表可以展开看全部,默认仍只列前 64 个
- 换任务时自动收起
v2.0.3
- 修好应用图标:之前打包没传图标参数,访达和 Dock 里显示的是打包工具的默认图标
- macOS 菜单栏常驻图标,菜单项跟桌面版一致:打开主窗口、加 URL、在访达显示下载目录、关于、设置、退出
- Windows 也用同一个图标
v2.0.2
- 一个包就够:命令行和后台守护都装在 app 里,首次启动自动装好
- macOS 把
sdm软链到 PATH 并注册开机自启 - Windows 首次运行时释放命令行和守护进程,加进 PATH 并注册计划任务
- macOS DMG 生成(
build.py --mac)
v2.0.1
Python 重写版。跨 macOS、Windows、Linux,界面换成液态玻璃。
- 引擎改用 httpx 真流式传输,字节到达即落盘
- 分片下载默认 32 段,上限 128
- 断点续传按已落盘字节继续
- 链接过期(403/410/451)时任务转「等待新链接」,换新 URL 后从已完成分段续传
- 全局限速、单任务限速、时间段限速规则
- 自动文件分类,无扩展名文件按 magic byte 识别
- 站点规则支持 Referer、User-Agent、Cookie、Basic Auth
- 下载历史、计划任务、站点规则、日志、通知
- 剪贴板候选确认、浏览器扩展派单
sdm命令行,每个命令返回 JSON- 后台 daemon,开机自启
- 窗口自由缩放,布局重排不裁切
- 详情页显示真实分片状态
- macOS DMG 生成(
build.py --mac)
Windows 22H2 与 macOS 12+。
v2.1.3
v2.1.3
修一个真正的架构 bug(下载任务根本跑不起来),再加上自动分类、全局 UA,
以及去掉手动分片数。
下载一直卡住、0 字节 —— 根因是事件循环被抹掉了
上一版把「卡在 0 KB/s」当成网络问题修了一轮(探测超时、重试预算)。
这次拿到进程内的 Python 栈才看清:下载任务压根没跑起来。
GuiBridge.set_window() 里有这么一段:从当前线程猜一个正在跑的
asyncio 循环,猜不到就把 _loop 置成 None。而窗口是在主线程里建的,
主线程上根本没有 asyncio 循环(引擎的循环在 sdm-engine 线程里)——
于是 set_window 把 set_loop() 刚设好的引擎循环抹成了 None。
_loop 是 None 之后,桥接层所有调用退化成 asyncio.run(coro):
一次性循环。engine.start() 在这个一次性循环里把下载任务
create_task 出去,asyncio.run 一返回就拆循环,任务在跑第一行之前
就被取消。结果就是:任务躺在列表里永远不动、0 字节、连一个 socket
都没建过(lsof 上看不到任何对外连接)。暂停/继续/取消也都打在错误的
循环上,所以怎么点都没反应。添加框里的「解析」能成功,是因为它自己
开循环同步取结果,不依赖引擎循环。
修法:
set_window()不再碰_loop——引擎循环只有一个来源:set_loop()main()里set_loop()挪到set_window()之后- 桥接层加了兜底:真的没设上循环时,自己起一个常驻的后台循环,
绝不再退化成一次性的asyncio.run - 连接池等待加了超时上限(
pool=None的意思是「等连接池空位时永不
超时」,池满了就永久挂起)
自动分类
- 分类下拉第一个选项是「自动」并且默认选中。早先前端把第一个选项
(视频)当成分类发给后端,后端再覆盖掉引擎按 URL 猜出来的正确分类
——用户加一个 .dmg,列表里却归在「视频」。现在「自动」传的是 null,
由引擎判断 - 按服务器给的文件名判分类:URL 常常是无扩展名的 CDN 路径
(/download/12345),真正的文件名在Content-Disposition里。
探测成功后按真实文件名回填分类(.dmg → 程序),保存目录也跟着换 - 用户手动改过分类就不再自动改
全局 UA
- 设置页新增「全局 User-Agent」:留空 = 用程序内置的默认值,
随时可以改,改完立刻对后续所有下载生效 - 优先级:站点规则里的 UA → 全局 UA → 内置默认 UA
- 清空输入框是「恢复默认」,不是「发一个空的 UA」
去掉手动分片数
添加对话框里那个默认 8 的分片数输入框没有了。分片本来就是引擎动态
规划的(起始连接数按文件大小和实测速度自己长),挂一个数字框只会
让人以为那个数字决定实际分片数。
其它
- 探测失败的日志从 notice 提到 warning——用户把日志级别调到 warning
时,那恰恰是排查「卡在连接中」最关键的一条线索 - 引擎探测之后会按真实文件名修正任务的分类(用户没手动选过时)
v2.1.2
修两件事:下载一直卡在 0 KB/s,以及下载列表的界面错位。
「0 KB/s」那条查出来是三件事叠在一起:探测探了两次、探测用的超时是
分片那个 60 秒的看门狗、重试预算只算退避不算请求本身的时间。三条
一起,服务器慢的时候就是几分钟的静默——界面全程显示 0 KB/s,
还没有报错。界面错位那条更严重:列表的 grid 模板压根没生效。
界面
- 下载列表的列布局修好了:表头和数据行用的是同一套 grid 模板
(--row-cols,名字列是1fr),但这个变量被写在了列表容器上,
而它里面的var(--w-*)只定义在行和表头上——CSS 自定义属性是在
声明它的那个元素上做替换的,容器上找不到--w-name,整个模板
就成了无效值,grid-template-columns退回none,所有列竖着
叠起来(实测一行 281px 高,八个格子竖排)。现在模板写在用它的
元素上,列宽和窄屏自适应一起恢复 - 表头和数据行不再差 36px:列选择器按钮原来跟表头并排(flex),
把表头挤窄了 36px,名字之后的每一列都整体错开。现在按钮浮在表头
右端,表头拿回满宽 - 数字列右对齐:表头的「大小 / 速度 / 剩余时间」是右对齐的,行里
的数字却是左对齐的,标签和数字永远对不上 - 连接阶段显示「连接中」:从任务开跑到收到第一个字节之间不再显示
「0 KB/s」——那段时间确实一个字节都没拿到,但「还没开始」和「卡死
了」是两回事 - 顺手修掉 i18n 里 7 处乱码文案(「SHA-256:(0)」这类被二次编码
过的字符串)
引擎
- 探测不再探两次:引擎探一次拿真实文件名和总长度,下载流程又探
一次——两个 RTT 的纯等待。现在探一次的结果直接交给流程层 - 探测有自己的短超时:探测以前借用分片的 60 秒 read 超时(那是给
慢连接用的空闲看门狗),服务器不回响应头时一次探测就静默 60 秒,
重试几轮就是好几分钟。现在探测用 15 秒的独立超时 - 重试预算真的约束总时间:预算以前只统计退避的 sleep,不统计请求
本身耗掉的时间——服务器不响应时预算永远花不出去。现在请求耗时也
计入预算,最长二十几秒就会给出明确的失败 - 探测失败给一句人话:以前界面上显示的是
ReadTimeout这种英文类名,
现在是「连接服务器超时 / 连不上服务器 / 网络错误」
验证
- 引擎侧新增
tests/test_connect_phase.py(探测预算、不重复探测、连接
态生命周期),前端新增tests/test_connecting_js.mjs和
tests/test_column_alignment_js.mjs - 列对齐用无头浏览器量像素验的(Chromium + WebKit 都跑过):修复
前一行 281px 高、八格竖排;修复后行高 56px,表头和数据行的 grid
轨道完全一致
已知限制
- 仍不支持 BT / 磁力
- 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.1
修三个用户实际撞上的问题:改设置后页面总跳回顶部、开启开机自启会立刻多出一个进程、
解析链接一直在报「所有重试都失败」。顺带修好进度条最后一段经常少报一截的问题。
界面
- 改设置不再跳回页面顶部:改任何设置都会重画整个设置页,而重画会把滚动位置一起
冲掉——用户改个设置就被弹回顶部。现在重画前记下位置、重画后放回去 - 任务被删掉导致列表重画时,设置页的滚动位置同样不再丢
macOS
- 开启开机自启不再立刻多出一个 SDM 进程:之前写完 plist 还顺手跑了一次
launchctl load,而 load 不只是「注册」,它当场就把任务跑起来了。自启的意思是
「下次登录时启动」,不是「现在启动」。现在只写 plist,登录时 launchd 自己会扫
引擎
- 磁力链接这类地址立刻失败并说清楚:磁力链接(
magnet:开头)以前会被当成网络
错误重试 5 次,最后报一句「所有重试都失败」——跟真实原因完全对不上。现在直接告诉你
「这是磁力链接(BT),SDM 只下载 HTTP(S) 的普通文件,不支持 BT/磁力」 - 不可重试的失败不再重试:404(文件不存在)、416(服务器不认这个 Range)这类重试
多少次结果都一样,现在一次就报错,不再白等 7.5 秒 - 两处错误分类对齐:新建任务对话框和任务列表现在用同一份分类。磁力链接在两边都
归到「客户端错误」,不再在任务列表里显示成「网络连接中断,将自动重试」还标着可
重试;404 归到「文件不存在」;429(被限流)归到「可重试」 - 进度条最后一段不再少报:下载快结束时进度会停在 98.97% 再也不动(2 MB 的文件
最终报成 2031616,正好少一个 64 KB 块)。根因是进度合并窗口到期时发的是上一个
窗口攒的旧值,把最后那次上报直接抹掉了 - 续传时进度不再倒退一次:续传的汇总基数没算盘上已有的字节,第一次上报会比实际
少,进度条会往回跳一下,紧接着算出来的速度也是个巨大的假值
已知限制
- 仍不支持 BT / 磁力(要做磁力得整个塞一个 BT 客户端进来,不在当前范围内);现在遇到
这类地址会立刻说清楚原因,而不是重试到天荒地老 - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.0
这一版按 DeepSeek 规格做了整轮补齐,重点在引擎和错误处理——这两块是真正
的壁垒,其余是体验放大器。
引擎
- 动态分段:分片布局在下载过程中会长大,不再是开跑前一次性切死。从 2 条
连接起步,有实打实的进度才逐级放开到设定上限;单个分片跑到一半会被拆成两半,
拆出来的另一半另开连接继续拉 - 无 Range 自动回退:服务器对 Range 请求返回 200 而不是 206 时,自动退化成
单流下载,不再把整份文件当成分片数据写坏 - 续传校验:会话文件里记了 ETag / Last-Modified / Content-Length,资源变了
就不再接着旧进度写,任务转成「资源已变更」而不是悄悄写出一个坏文件 - 会话落盘:分片布局写成
<文件名>.sdm.session.json,崩溃或被杀之后能按原
布局接着下 - 进度报增量:文件级进度是所有分片增量之和。修好了进度虚高 2.5 倍的 bug
(2 MB 的文件一度报成 5 MB) - 代理:支持 http / https 原生代理,SOCKS4 / SOCKS5 走
httpx-socks - 校验和:可选 md5 / sha1 / sha256,完成后自动算出校验值
- 完成后行为:打开文件、打开文件夹、跑脚本、休眠,可按任务设置
- 磁盘空间预检:开跑前检查剩余空间,不够就直接失败,不下到一半才发现
- 名字冲突:默认保留两者(
foo (1).zip),覆盖要走「移到废纸篓」,不会
静默删掉用户已有文件 - 任务队列:同时活跃的任务数有上限(默认 5),超出的排队,可置顶置底
- 重试与改选项:失败任务可重试,代理 / 校验算法 / 完成后行为可以中途改
错误处理
- 12 类结构化错误分类:网络、磁盘、权限、空间不足、认证、链接过期、服务器、
限流、协议、不支持、用户取消、未知 - 每类错误都带能不能自动重试和用户该做什么的中文说明
- HTTP 状态码自动归类,403 / 410 / 451 归到「链接失效」,任务转「等待新链接」
- 尊重
Retry-After,重试用指数退避 + 抖动,最多 5 次 - 错误详情可以复制出来,粘到反馈里能看懂
浏览器接管
- Native Messaging 宿主:Chrome / Edge / Firefox / Safari 扩展通过原生消息
把下载派给应用,自动带上页面的 Cookie、User-Agent、Referer - 配对令牌:扩展第一次连接要跟应用配对,令牌存在本地,回环 HTTP 通道强制
校验 - Chrome / Edge 扩展重写:取消浏览器自带的下载,接管文件名、按分类分目录
- Firefox 修好:以前把文件名当 URL 发出去,压根下不动
- Safari 改成纯手动:右键菜单 + 工具栏按钮,不做自动拦截
界面
- 底部状态栏:全局速度、限速、活跃任务数
- 分类列 + 列显隐选择器
- 新建任务对话框重写:多行 URL、保存目录、分类、代理、请求头键值行、分块数、
开始时机(立即 / 排队 / 定时),带解析状态和重新解析 - 详情面板:分片可视化 + 最近 60 秒的速度曲线
- 浅色 / 深色主题切换,跟随系统
- 设置里补齐 7 个新键(并发任务数、主题、代理、配对令牌、初始连接数、分片上限等)
macOS / Windows 系统集成
- 菜单栏状态行:实时显示「下载中 N 个 · 速度」,1.5 秒刷一次;带暂停全部、
继续全部、剪贴板监控勾选 - Dock 图标显示整体下载进度
- AppleScript 词典:可在 Script Editor 里
add download、pause all downloads、
resume all downloads、get download list - Windows 托盘图标 + 与 macOS 一致的菜单
- Windows URL Scheme 注册表项
打包
- 新增
sdmd-native-host产物,浏览器按清单拉起它 - macOS 首次启动自动装原生消息宿主清单,Windows 解包后自动装
- AppleScript 词典打进
.app,Info.plist里写OSAScriptingDefinition
已知限制
- Safari 只做手动接管,不做自动拦截
- 不支持 BT / 磁力
- 没有 App Intents 原生集成
- SOCKS 代理需要额外装
httpx-socks - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.0.6
- 下载列表可以按文件名、大小、速度、状态排序,未知大小的任务排在最后
- 速度显示改成平滑曲线,不再忽快忽慢;任务结束或暂停时速度归零
- 下载完成后自动写进历史记录
- 日志每天自动清理一次,改动日志设置时也会立即清理
- 设置里可以直接打开 macOS 的 Console.app 看日志
- 下载完成和失败时发系统通知;应用在前台时也弹横幅;点通知会把主窗口拉到前面
- 站点规则对探测请求也生效,需要 Referer 或特定 UA 的站点不再一启动就失败
- 命令行和后台守护支持用环境变量指定 socket 路径
- 后台守护必须由系统服务启动,手动运行会给出提示而不是挂在前台
- 剪贴板候选弹窗可以随时停止监听;已经在下载队列里的链接不再重复提示
- 关闭自动分类后,所有下载落进下载目录根目录
- 关掉主窗口后应用继续在菜单栏待着,可从菜单栏唤回
- 关闭窗口时按设置隐藏 Dock 图标,从菜单栏唤回时恢复
- 启动时自动同步开机自启状态
- 带宽限制输入框的单位改成 KB/s,之前填的数值实际是按字节算的
- 支持
superdownloadmanager://链接唤起下载
v2.0.5
- 修好分片计数:详情页的「已完成分片 / 总片数」以前一直是 0/0,现在正确显示
- 最后一个分片完成后状态不再停留在未完成
- 已完成的任务也能查看分片明细
- 修好 socket 路径过长时 daemon 起不来:路径超过系统上限自动改用短路径
- 打包时校验应用图标,没打进 bundle 直接让构建失败
v2.0.4
- 详情页分片表可以展开看全部,默认仍只列前 64 个
- 换任务时自动收起
v2.0.3
- 修好应用图标:之前打包没传图标参数,访达和 Dock 里显示的是打包工具的默认图标
- macOS 菜单栏常驻图标,菜单项跟桌面版一致:打开主窗口、加 URL、在访达显示下载目录、关于、设置、退出
- Windows 也用同一个图标
v2.0.2
- 一个包就够:命令行和后台守护都装在 app 里,首次启动自动装好
- macOS 把
sdm软链到 PATH 并注册开机自启 - Windows 首次运行时释放命令行和守护进程,加进 PATH 并注册计划任务
- macOS DMG 生成(
build.py --mac)
v2.0.1
Python 重写版。跨 macOS、Windows、Linux,界面换成液态玻璃。
- 引擎改用 httpx 真流式传输,字节到达即落盘
- 分片下载默认 32 段,上限 128
- 断点续传按已落盘字节继续
- 链接过期(403/410/451)时任务转「等待新链接」,换新 URL 后从已完成分段续传
- 全局限速、单任务限速、时间段限速规则
- 自动文件分类,无扩展名文件按 magic byte 识别
- 站点规则支持 Referer、User-Agent、Cookie、Basic Auth
- 下载历史、计划任务、站点规则、日志、通知
- 剪贴板候选确认、浏览器扩展派单
sdm命令行,每个命令返回 JSON- 后台 daemon,开机自启
- 窗口自由缩放,布局重排不裁切
- 详情页显示真实分片状态
- macOS DMG 生成(
build.py --mac)
Windows 22H2 与 macOS 12+。
v2.1.2
v2.1.2
修两件事:下载一直卡在 0 KB/s,以及下载列表的界面错位。
「0 KB/s」那条查出来是三件事叠在一起:探测探了两次、探测用的超时是
分片那个 60 秒的看门狗、重试预算只算退避不算请求本身的时间。三条
一起,服务器慢的时候就是几分钟的静默——界面全程显示 0 KB/s,
还没有报错。界面错位那条更严重:列表的 grid 模板压根没生效。
界面
- 下载列表的列布局修好了:表头和数据行用的是同一套 grid 模板
(--row-cols,名字列是1fr),但这个变量被写在了列表容器上,
而它里面的var(--w-*)只定义在行和表头上——CSS 自定义属性是在
声明它的那个元素上做替换的,容器上找不到--w-name,整个模板
就成了无效值,grid-template-columns退回none,所有列竖着
叠起来(实测一行 281px 高,八个格子竖排)。现在模板写在用它的
元素上,列宽和窄屏自适应一起恢复 - 表头和数据行不再差 36px:列选择器按钮原来跟表头并排(flex),
把表头挤窄了 36px,名字之后的每一列都整体错开。现在按钮浮在表头
右端,表头拿回满宽 - 数字列右对齐:表头的「大小 / 速度 / 剩余时间」是右对齐的,行里
的数字却是左对齐的,标签和数字永远对不上 - 连接阶段显示「连接中」:从任务开跑到收到第一个字节之间不再显示
「0 KB/s」——那段时间确实一个字节都没拿到,但「还没开始」和「卡死
了」是两回事 - 顺手修掉 i18n 里 7 处乱码文案(「SHA-256:(0)」这类被二次编码
过的字符串)
引擎
- 探测不再探两次:引擎探一次拿真实文件名和总长度,下载流程又探
一次——两个 RTT 的纯等待。现在探一次的结果直接交给流程层 - 探测有自己的短超时:探测以前借用分片的 60 秒 read 超时(那是给
慢连接用的空闲看门狗),服务器不回响应头时一次探测就静默 60 秒,
重试几轮就是好几分钟。现在探测用 15 秒的独立超时 - 重试预算真的约束总时间:预算以前只统计退避的 sleep,不统计请求
本身耗掉的时间——服务器不响应时预算永远花不出去。现在请求耗时也
计入预算,最长二十几秒就会给出明确的失败 - 探测失败给一句人话:以前界面上显示的是
ReadTimeout这种英文类名,
现在是「连接服务器超时 / 连不上服务器 / 网络错误」
验证
- 引擎侧新增
tests/test_connect_phase.py(探测预算、不重复探测、连接
态生命周期),前端新增tests/test_connecting_js.mjs和
tests/test_column_alignment_js.mjs - 列对齐用无头浏览器量像素验的(Chromium + WebKit 都跑过):修复
前一行 281px 高、八格竖排;修复后行高 56px,表头和数据行的 grid
轨道完全一致
已知限制
- 仍不支持 BT / 磁力
- 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.1
修三个用户实际撞上的问题:改设置后页面总跳回顶部、开启开机自启会立刻多出一个进程、
解析链接一直在报「所有重试都失败」。顺带修好进度条最后一段经常少报一截的问题。
界面
- 改设置不再跳回页面顶部:改任何设置都会重画整个设置页,而重画会把滚动位置一起
冲掉——用户改个设置就被弹回顶部。现在重画前记下位置、重画后放回去 - 任务被删掉导致列表重画时,设置页的滚动位置同样不再丢
macOS
- 开启开机自启不再立刻多出一个 SDM 进程:之前写完 plist 还顺手跑了一次
launchctl load,而 load 不只是「注册」,它当场就把任务跑起来了。自启的意思是
「下次登录时启动」,不是「现在启动」。现在只写 plist,登录时 launchd 自己会扫
引擎
- 磁力链接这类地址立刻失败并说清楚:磁力链接(
magnet:开头)以前会被当成网络
错误重试 5 次,最后报一句「所有重试都失败」——跟真实原因完全对不上。现在直接告诉你
「这是磁力链接(BT),SDM 只下载 HTTP(S) 的普通文件,不支持 BT/磁力」 - 不可重试的失败不再重试:404(文件不存在)、416(服务器不认这个 Range)这类重试
多少次结果都一样,现在一次就报错,不再白等 7.5 秒 - 两处错误分类对齐:新建任务对话框和任务列表现在用同一份分类。磁力链接在两边都
归到「客户端错误」,不再在任务列表里显示成「网络连接中断,将自动重试」还标着可
重试;404 归到「文件不存在」;429(被限流)归到「可重试」 - 进度条最后一段不再少报:下载快结束时进度会停在 98.97% 再也不动(2 MB 的文件
最终报成 2031616,正好少一个 64 KB 块)。根因是进度合并窗口到期时发的是上一个
窗口攒的旧值,把最后那次上报直接抹掉了 - 续传时进度不再倒退一次:续传的汇总基数没算盘上已有的字节,第一次上报会比实际
少,进度条会往回跳一下,紧接着算出来的速度也是个巨大的假值
已知限制
- 仍不支持 BT / 磁力(要做磁力得整个塞一个 BT 客户端进来,不在当前范围内);现在遇到
这类地址会立刻说清楚原因,而不是重试到天荒地老 - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.0
这一版按 DeepSeek 规格做了整轮补齐,重点在引擎和错误处理——这两块是真正
的壁垒,其余是体验放大器。
引擎
- 动态分段:分片布局在下载过程中会长大,不再是开跑前一次性切死。从 2 条
连接起步,有实打实的进度才逐级放开到设定上限;单个分片跑到一半会被拆成两半,
拆出来的另一半另开连接继续拉 - 无 Range 自动回退:服务器对 Range 请求返回 200 而不是 206 时,自动退化成
单流下载,不再把整份文件当成分片数据写坏 - 续传校验:会话文件里记了 ETag / Last-Modified / Content-Length,资源变了
就不再接着旧进度写,任务转成「资源已变更」而不是悄悄写出一个坏文件 - 会话落盘:分片布局写成
<文件名>.sdm.session.json,崩溃或被杀之后能按原
布局接着下 - 进度报增量:文件级进度是所有分片增量之和。修好了进度虚高 2.5 倍的 bug
(2 MB 的文件一度报成 5 MB) - 代理:支持 http / https 原生代理,SOCKS4 / SOCKS5 走
httpx-socks - 校验和:可选 md5 / sha1 / sha256,完成后自动算出校验值
- 完成后行为:打开文件、打开文件夹、跑脚本、休眠,可按任务设置
- 磁盘空间预检:开跑前检查剩余空间,不够就直接失败,不下到一半才发现
- 名字冲突:默认保留两者(
foo (1).zip),覆盖要走「移到废纸篓」,不会
静默删掉用户已有文件 - 任务队列:同时活跃的任务数有上限(默认 5),超出的排队,可置顶置底
- 重试与改选项:失败任务可重试,代理 / 校验算法 / 完成后行为可以中途改
错误处理
- 12 类结构化错误分类:网络、磁盘、权限、空间不足、认证、链接过期、服务器、
限流、协议、不支持、用户取消、未知 - 每类错误都带能不能自动重试和用户该做什么的中文说明
- HTTP 状态码自动归类,403 / 410 / 451 归到「链接失效」,任务转「等待新链接」
- 尊重
Retry-After,重试用指数退避 + 抖动,最多 5 次 - 错误详情可以复制出来,粘到反馈里能看懂
浏览器接管
- Native Messaging 宿主:Chrome / Edge / Firefox / Safari 扩展通过原生消息
把下载派给应用,自动带上页面的 Cookie、User-Agent、Referer - 配对令牌:扩展第一次连接要跟应用配对,令牌存在本地,回环 HTTP 通道强制
校验 - Chrome / Edge 扩展重写:取消浏览器自带的下载,接管文件名、按分类分目录
- Firefox 修好:以前把文件名当 URL 发出去,压根下不动
- Safari 改成纯手动:右键菜单 + 工具栏按钮,不做自动拦截
界面
- 底部状态栏:全局速度、限速、活跃任务数
- 分类列 + 列显隐选择器
- 新建任务对话框重写:多行 URL、保存目录、分类、代理、请求头键值行、分块数、
开始时机(立即 / 排队 / 定时),带解析状态和重新解析 - 详情面板:分片可视化 + 最近 60 秒的速度曲线
- 浅色 / 深色主题切换,跟随系统
- 设置里补齐 7 个新键(并发任务数、主题、代理、配对令牌、初始连接数、分片上限等)
macOS / Windows 系统集成
- 菜单栏状态行:实时显示「下载中 N 个 · 速度」,1.5 秒刷一次;带暂停全部、
继续全部、剪贴板监控勾选 - Dock 图标显示整体下载进度
- AppleScript 词典:可在 Script Editor 里
add download、pause all downloads、
resume all downloads、get download list - Windows 托盘图标 + 与 macOS 一致的菜单
- Windows URL Scheme 注册表项
打包
- 新增
sdmd-native-host产物,浏览器按清单拉起它 - macOS 首次启动自动装原生消息宿主清单,Windows 解包后自动装
- AppleScript 词典打进
.app,Info.plist里写OSAScriptingDefinition
已知限制
- Safari 只做手动接管,不做自动拦截
- 不支持 BT / 磁力
- 没有 App Intents 原生集成
- SOCKS 代理需要额外装
httpx-socks - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.0.6
- 下载列表可以按文件名、大小、速度、状态排序,未知大小的任务排在最后
- 速度显示改成平滑曲线,不再忽快忽慢;任务结束或暂停时速度归零
- 下载完成后自动写进历史记录
- 日志每天自动清理一次,改动日志设置时也会立即清理
- 设置里可以直接打开 macOS 的 Console.app 看日志
- 下载完成和失败时发系统通知;应用在前台时也弹横幅;点通知会把主窗口拉到前面
- 站点规则对探测请求也生效,需要 Referer 或特定 UA 的站点不再一启动就失败
- 命令行和后台守护支持用环境变量指定 socket 路径
- 后台守护必须由系统服务启动,手动运行会给出提示而不是挂在前台
- 剪贴板候选弹窗可以随时停止监听;已经在下载队列里的链接不再重复提示
- 关闭自动分类后,所有下载落进下载目录根目录
- 关掉主窗口后应用继续在菜单栏待着,可从菜单栏唤回
- 关闭窗口时按设置隐藏 Dock 图标,从菜单栏唤回时恢复
- 启动时自动同步开机自启状态
- 带宽限制输入框的单位改成 KB/s,之前填的数值实际是按字节算的
- 支持
superdownloadmanager://链接唤起下载
v2.0.5
- 修好分片计数:详情页的「已完成分片 / 总片数」以前一直是 0/0,现在正确显示
- 最后一个分片完成后状态不再停留在未完成
- 已完成的任务也能查看分片明细
- 修好 socket 路径过长时 daemon 起不来:路径超过系统上限自动改用短路径
- 打包时校验应用图标,没打进 bundle 直接让构建失败
v2.0.4
- 详情页分片表可以展开看全部,默认仍只列前 64 个
- 换任务时自动收起
v2.0.3
- 修好应用图标:之前打包没传图标参数,访达和 Dock 里显示的是打包工具的默认图标
- macOS 菜单栏常驻图标,菜单项跟桌面版一致:打开主窗口、加 URL、在访达显示下载目录、关于、设置、退出
- Windows 也用同一个图标
v2.0.2
- 一个包就够:命令行和后台守护都装在 app 里,首次启动自动装好
- macOS 把
sdm软链到 PATH 并注册开机自启 - Windows 首次运行时释放命令行和守护进程,加进 PATH 并注册计划任务
- macOS DMG 生成(
build.py --mac)
v2.0.1
Python 重写版。跨 macOS、Windows、Linux,界面换成液态玻璃。
- 引擎改用 httpx 真流式传输,字节到达即落盘
- 分片下载默认 32 段,上限 128
- 断点续传按已落盘字节继续
- 链接过期(403/410/451)时任务转「等待新链接」,换新 URL 后从已完成分段续传
- 全局限速、单任务限速、时间段限速规则
- 自动文件分类,无扩展名文件按 magic byte 识别
- 站点规则支持 Referer、User-Agent、Cookie、Basic Auth
- 下载历史、计划任务、站点规则、日志、通知
- 剪贴板候选确认、浏览器扩展派单
sdm命令行,每个命令返回 JSON- 后台 daemon,开机自启
- 窗口自由缩放,布局重排不裁切
- 详情页显示真实分片状态
- macOS DMG 生成(
build.py --mac)
Windows 22H2 与 macOS 12+。
v2.1.1
v2.1.1
修三个用户实际撞上的问题:改设置后页面总跳回顶部、开启开机自启会立刻多出一个进程、
解析链接一直在报「所有重试都失败」。顺带修好进度条最后一段经常少报一截的问题。
界面
- 改设置不再跳回页面顶部:改任何设置都会重画整个设置页,而重画会把滚动位置一起
冲掉——用户改个设置就被弹回顶部。现在重画前记下位置、重画后放回去 - 任务被删掉导致列表重画时,设置页的滚动位置同样不再丢
macOS
- 开启开机自启不再立刻多出一个 SDM 进程:之前写完 plist 还顺手跑了一次
launchctl load,而 load 不只是「注册」,它当场就把任务跑起来了。自启的意思是
「下次登录时启动」,不是「现在启动」。现在只写 plist,登录时 launchd 自己会扫
引擎
- 磁力链接这类地址立刻失败并说清楚:磁力链接(
magnet:开头)以前会被当成网络
错误重试 5 次,最后报一句「所有重试都失败」——跟真实原因完全对不上。现在直接告诉你
「这是磁力链接(BT),SDM 只下载 HTTP(S) 的普通文件,不支持 BT/磁力」 - 不可重试的失败不再重试:404(文件不存在)、416(服务器不认这个 Range)这类重试
多少次结果都一样,现在一次就报错,不再白等 7.5 秒 - 两处错误分类对齐:新建任务对话框和任务列表现在用同一份分类。磁力链接在两边都
归到「客户端错误」,不再在任务列表里显示成「网络连接中断,将自动重试」还标着可
重试;404 归到「文件不存在」;429(被限流)归到「可重试」 - 进度条最后一段不再少报:下载快结束时进度会停在 98.97% 再也不动(2 MB 的文件
最终报成 2031616,正好少一个 64 KB 块)。根因是进度合并窗口到期时发的是上一个
窗口攒的旧值,把最后那次上报直接抹掉了 - 续传时进度不再倒退一次:续传的汇总基数没算盘上已有的字节,第一次上报会比实际
少,进度条会往回跳一下,紧接着算出来的速度也是个巨大的假值
已知限制
- 仍不支持 BT / 磁力(要做磁力得整个塞一个 BT 客户端进来,不在当前范围内);现在遇到
这类地址会立刻说清楚原因,而不是重试到天荒地老 - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.1.0
这一版按 DeepSeek 规格做了整轮补齐,重点在引擎和错误处理——这两块是真正
的壁垒,其余是体验放大器。
引擎
- 动态分段:分片布局在下载过程中会长大,不再是开跑前一次性切死。从 2 条
连接起步,有实打实的进度才逐级放开到设定上限;单个分片跑到一半会被拆成两半,
拆出来的另一半另开连接继续拉 - 无 Range 自动回退:服务器对 Range 请求返回 200 而不是 206 时,自动退化成
单流下载,不再把整份文件当成分片数据写坏 - 续传校验:会话文件里记了 ETag / Last-Modified / Content-Length,资源变了
就不再接着旧进度写,任务转成「资源已变更」而不是悄悄写出一个坏文件 - 会话落盘:分片布局写成
<文件名>.sdm.session.json,崩溃或被杀之后能按原
布局接着下 - 进度报增量:文件级进度是所有分片增量之和。修好了进度虚高 2.5 倍的 bug
(2 MB 的文件一度报成 5 MB) - 代理:支持 http / https 原生代理,SOCKS4 / SOCKS5 走
httpx-socks - 校验和:可选 md5 / sha1 / sha256,完成后自动算出校验值
- 完成后行为:打开文件、打开文件夹、跑脚本、休眠,可按任务设置
- 磁盘空间预检:开跑前检查剩余空间,不够就直接失败,不下到一半才发现
- 名字冲突:默认保留两者(
foo (1).zip),覆盖要走「移到废纸篓」,不会
静默删掉用户已有文件 - 任务队列:同时活跃的任务数有上限(默认 5),超出的排队,可置顶置底
- 重试与改选项:失败任务可重试,代理 / 校验算法 / 完成后行为可以中途改
错误处理
- 12 类结构化错误分类:网络、磁盘、权限、空间不足、认证、链接过期、服务器、
限流、协议、不支持、用户取消、未知 - 每类错误都带能不能自动重试和用户该做什么的中文说明
- HTTP 状态码自动归类,403 / 410 / 451 归到「链接失效」,任务转「等待新链接」
- 尊重
Retry-After,重试用指数退避 + 抖动,最多 5 次 - 错误详情可以复制出来,粘到反馈里能看懂
浏览器接管
- Native Messaging 宿主:Chrome / Edge / Firefox / Safari 扩展通过原生消息
把下载派给应用,自动带上页面的 Cookie、User-Agent、Referer - 配对令牌:扩展第一次连接要跟应用配对,令牌存在本地,回环 HTTP 通道强制
校验 - Chrome / Edge 扩展重写:取消浏览器自带的下载,接管文件名、按分类分目录
- Firefox 修好:以前把文件名当 URL 发出去,压根下不动
- Safari 改成纯手动:右键菜单 + 工具栏按钮,不做自动拦截
界面
- 底部状态栏:全局速度、限速、活跃任务数
- 分类列 + 列显隐选择器
- 新建任务对话框重写:多行 URL、保存目录、分类、代理、请求头键值行、分块数、
开始时机(立即 / 排队 / 定时),带解析状态和重新解析 - 详情面板:分片可视化 + 最近 60 秒的速度曲线
- 浅色 / 深色主题切换,跟随系统
- 设置里补齐 7 个新键(并发任务数、主题、代理、配对令牌、初始连接数、分片上限等)
macOS / Windows 系统集成
- 菜单栏状态行:实时显示「下载中 N 个 · 速度」,1.5 秒刷一次;带暂停全部、
继续全部、剪贴板监控勾选 - Dock 图标显示整体下载进度
- AppleScript 词典:可在 Script Editor 里
add download、pause all downloads、
resume all downloads、get download list - Windows 托盘图标 + 与 macOS 一致的菜单
- Windows URL Scheme 注册表项
打包
- 新增
sdmd-native-host产物,浏览器按清单拉起它 - macOS 首次启动自动装原生消息宿主清单,Windows 解包后自动装
- AppleScript 词典打进
.app,Info.plist里写OSAScriptingDefinition
已知限制
- Safari 只做手动接管,不做自动拦截
- 不支持 BT / 磁力
- 没有 App Intents 原生集成
- SOCKS 代理需要额外装
httpx-socks - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.0.6
- 下载列表可以按文件名、大小、速度、状态排序,未知大小的任务排在最后
- 速度显示改成平滑曲线,不再忽快忽慢;任务结束或暂停时速度归零
- 下载完成后自动写进历史记录
- 日志每天自动清理一次,改动日志设置时也会立即清理
- 设置里可以直接打开 macOS 的 Console.app 看日志
- 下载完成和失败时发系统通知;应用在前台时也弹横幅;点通知会把主窗口拉到前面
- 站点规则对探测请求也生效,需要 Referer 或特定 UA 的站点不再一启动就失败
- 命令行和后台守护支持用环境变量指定 socket 路径
- 后台守护必须由系统服务启动,手动运行会给出提示而不是挂在前台
- 剪贴板候选弹窗可以随时停止监听;已经在下载队列里的链接不再重复提示
- 关闭自动分类后,所有下载落进下载目录根目录
- 关掉主窗口后应用继续在菜单栏待着,可从菜单栏唤回
- 关闭窗口时按设置隐藏 Dock 图标,从菜单栏唤回时恢复
- 启动时自动同步开机自启状态
- 带宽限制输入框的单位改成 KB/s,之前填的数值实际是按字节算的
- 支持
superdownloadmanager://链接唤起下载
v2.0.5
- 修好分片计数:详情页的「已完成分片 / 总片数」以前一直是 0/0,现在正确显示
- 最后一个分片完成后状态不再停留在未完成
- 已完成的任务也能查看分片明细
- 修好 socket 路径过长时 daemon 起不来:路径超过系统上限自动改用短路径
- 打包时校验应用图标,没打进 bundle 直接让构建失败
v2.0.4
- 详情页分片表可以展开看全部,默认仍只列前 64 个
- 换任务时自动收起
v2.0.3
- 修好应用图标:之前打包没传图标参数,访达和 Dock 里显示的是打包工具的默认图标
- macOS 菜单栏常驻图标,菜单项跟桌面版一致:打开主窗口、加 URL、在访达显示下载目录、关于、设置、退出
- Windows 也用同一个图标
v2.0.2
- 一个包就够:命令行和后台守护都装在 app 里,首次启动自动装好
- macOS 把
sdm软链到 PATH 并注册开机自启 - Windows 首次运行时释放命令行和守护进程,加进 PATH 并注册计划任务
- macOS DMG 生成(
build.py --mac)
v2.0.1
Python 重写版。跨 macOS、Windows、Linux,界面换成液态玻璃。
- 引擎改用 httpx 真流式传输,字节到达即落盘
- 分片下载默认 32 段,上限 128
- 断点续传按已落盘字节继续
- 链接过期(403/410/451)时任务转「等待新链接」,换新 URL 后从已完成分段续传
- 全局限速、单任务限速、时间段限速规则
- 自动文件分类,无扩展名文件按 magic byte 识别
- 站点规则支持 Referer、User-Agent、Cookie、Basic Auth
- 下载历史、计划任务、站点规则、日志、通知
- 剪贴板候选确认、浏览器扩展派单
sdm命令行,每个命令返回 JSON- 后台 daemon,开机自启
- 窗口自由缩放,布局重排不裁切
- 详情页显示真实分片状态
- macOS DMG 生成(
build.py --mac)
Windows 22H2 与 macOS 12+。
v2.1.0
v2.1.0
引擎
- 动态分段:分片布局在下载过程中会长大,不再是开跑前一次性切死。从 2 条
连接起步,有实打实的进度才逐级放开到设定上限;单个分片跑到一半会被拆成两半,
拆出来的另一半另开连接继续拉 - 无 Range 自动回退:服务器对 Range 请求返回 200 而不是 206 时,自动退化成
单流下载,不再把整份文件当成分片数据写坏 - 续传校验:会话文件里记了 ETag / Last-Modified / Content-Length,资源变了
就不再接着旧进度写,任务转成「资源已变更」而不是悄悄写出一个坏文件 - 会话落盘:分片布局写成
<文件名>.sdm.session.json,崩溃或被杀之后能按原
布局接着下 - 进度报增量:文件级进度是所有分片增量之和。修好了进度虚高 2.5 倍的 bug
(2 MB 的文件一度报成 5 MB) - 代理:支持 http / https 原生代理,SOCKS4 / SOCKS5 走
httpx-socks - 校验和:可选 md5 / sha1 / sha256,完成后自动算出校验值
- 完成后行为:打开文件、打开文件夹、跑脚本、休眠,可按任务设置
- 磁盘空间预检:开跑前检查剩余空间,不够就直接失败,不下到一半才发现
- 名字冲突:默认保留两者(
foo (1).zip),覆盖要走「移到废纸篓」,不会
静默删掉用户已有文件 - 任务队列:同时活跃的任务数有上限(默认 5),超出的排队,可置顶置底
- 重试与改选项:失败任务可重试,代理 / 校验算法 / 完成后行为可以中途改
错误处理
- 12 类结构化错误分类:网络、磁盘、权限、空间不足、认证、链接过期、服务器、
限流、协议、不支持、用户取消、未知 - 每类错误都带能不能自动重试和用户该做什么的中文说明
- HTTP 状态码自动归类,403 / 410 / 451 归到「链接失效」,任务转「等待新链接」
- 尊重
Retry-After,重试用指数退避 + 抖动,最多 5 次 - 错误详情可以复制出来,粘到反馈里能看懂
浏览器接管
- Native Messaging 宿主:Chrome / Edge / Firefox / Safari 扩展通过原生消息
把下载派给应用,自动带上页面的 Cookie、User-Agent、Referer - 配对令牌:扩展第一次连接要跟应用配对,令牌存在本地,回环 HTTP 通道强制
校验 - Chrome / Edge 扩展重写:取消浏览器自带的下载,接管文件名、按分类分目录
- Firefox 修好:以前把文件名当 URL 发出去,压根下不动
- Safari 改成纯手动:右键菜单 + 工具栏按钮,不做自动拦截
界面
- 底部状态栏:全局速度、限速、活跃任务数
- 分类列 + 列显隐选择器
- 新建任务对话框重写:多行 URL、保存目录、分类、代理、请求头键值行、分块数、
开始时机(立即 / 排队 / 定时),带解析状态和重新解析 - 详情面板:分片可视化 + 最近 60 秒的速度曲线
- 浅色 / 深色主题切换,跟随系统
- 设置里补齐 7 个新键(并发任务数、主题、代理、配对令牌、初始连接数、分片上限等)
macOS / Windows 系统集成
- 菜单栏状态行:实时显示「下载中 N 个 · 速度」,1.5 秒刷一次;带暂停全部、
继续全部、剪贴板监控勾选 - Dock 图标显示整体下载进度
- AppleScript 词典:可在 Script Editor 里
add download、pause all downloads、
resume all downloads、get download list - Windows 托盘图标 + 与 macOS 一致的菜单
- Windows URL Scheme 注册表项
打包
- 新增
sdmd-native-host产物,浏览器按清单拉起它 - macOS 首次启动自动装原生消息宿主清单,Windows 解包后自动装
- AppleScript 词典打进
.app,Info.plist里写OSAScriptingDefinition
已知限制
- Safari 只做手动接管,不做自动拦截
- 不支持 BT / 磁力
- 没有 App Intents 原生集成
- SOCKS 代理需要额外装
httpx-socks - 本版仍未经过大规模手测,按惯例发 Pre-release
v2.0.6
- 下载列表可以按文件名、大小、速度、状态排序,未知大小的任务排在最后
- 速度显示改成平滑曲线,不再忽快忽慢;任务结束或暂停时速度归零
- 下载完成后自动写进历史记录
- 日志每天自动清理一次,改动日志设置时也会立即清理
- 设置里可以直接打开 macOS 的 Console.app 看日志
- 下载完成和失败时发系统通知;应用在前台时也弹横幅;点通知会把主窗口拉到前面
- 站点规则对探测请求也生效,需要 Referer 或特定 UA 的站点不再一启动就失败
- 命令行和后台守护支持用环境变量指定 socket 路径
- 后台守护必须由系统服务启动,手动运行会给出提示而不是挂在前台
- 剪贴板候选弹窗可以随时停止监听;已经在下载队列里的链接不再重复提示
- 关闭自动分类后,所有下载落进下载目录根目录
- 关掉主窗口后应用继续在菜单栏待着,可从菜单栏唤回
- 关闭窗口时按设置隐藏 Dock 图标,从菜单栏唤回时恢复
- 启动时自动同步开机自启状态
- 带宽限制输入框的单位改成 KB/s,之前填的数值实际是按字节算的
- 支持
superdownloadmanager://链接唤起下载
v2.0.5
- 修好分片计数:详情页的「已完成分片 / 总片数」以前一直是 0/0,现在正确显示
- 最后一个分片完成后状态不再停留在未完成
- 已完成的任务也能查看分片明细
- 修好 socket 路径过长时 daemon 起不来:路径超过系统上限自动改用短路径
- 打包时校验应用图标,没打进 bundle 直接让构建失败
v2.0.4
- 详情页分片表可以展开看全部,默认仍只列前 64 个
- 换任务时自动收起
v2.0.3
- 修好应用图标:之前打包没传图标参数,访达和 Dock 里显示的是打包工具的默认图标
- macOS 菜单栏常驻图标,菜单项跟桌面版一致:打开主窗口、加 URL、在访达显示下载目录、关于、设置、退出
- Windows 也用同一个图标
v2.0.2
- 一个包就够:命令行和后台守护都装在 app 里,首次启动自动装好
- macOS 把
sdm软链到 PATH 并注册开机自启 - Windows 首次运行时释放命令行和守护进程,加进 PATH 并注册计划任务
- macOS DMG 生成(
build.py --mac)
v2.0.1
Python 重写版。跨 macOS、Windows、Linux,界面换成液态玻璃。
- 引擎改用 httpx 真流式传输,字节到达即落盘
- 分片下载默认 32 段,上限 128
- 断点续传按已落盘字节继续
- 链接过期(403/410/451)时任务转「等待新链接」,换新 URL 后从已完成分段续传
- 全局限速、单任务限速、时间段限速规则
- 自动文件分类,无扩展名文件按 magic byte 识别
- 站点规则支持 Referer、User-Agent、Cookie、Basic Auth
- 下载历史、计划任务、站点规则、日志、通知
- 剪贴板候选确认、浏览器扩展派单
sdm命令行,每个命令返回 JSON- 后台 daemon,开机自启
- 窗口自由缩放,布局重排不裁切
- 详情页显示真实分片状态
- macOS DMG 生成(
build.py --mac)
Windows 22H2 与 macOS 12+。
v2.0.6
v2.0.6
- 下载列表可以按文件名、大小、速度、状态排序,未知大小的任务排在最后
- 速度显示改成平滑曲线,不再忽快忽慢;任务结束或暂停时速度归零
- 下载完成后自动写进历史记录
- 日志每天自动清理一次,改动日志设置时也会立即清理
- 设置里可以直接打开 macOS 的 Console.app 看日志
- 下载完成和失败时发系统通知;应用在前台时也弹横幅;点通知会把主窗口拉到前面
- 站点规则对探测请求也生效,需要 Referer 或特定 UA 的站点不再一启动就失败
- 命令行和后台守护支持用环境变量指定 socket 路径
- 后台守护必须由系统服务启动,手动运行会给出提示而不是挂在前台
- 剪贴板候选弹窗可以随时停止监听;已经在下载队列里的链接不再重复提示
- 关闭自动分类后,所有下载落进下载目录根目录
- 关掉主窗口后应用继续在菜单栏待着,可从菜单栏唤回
- 关闭窗口时按设置隐藏 Dock 图标,从菜单栏唤回时恢复
- 启动时自动同步开机自启状态
- 带宽限制输入框的单位改成 KB/s,之前填的数值实际是按字节算的
- 支持
superdownloadmanager://链接唤起下载
v2.0.5
- 修好分片计数:详情页的「已完成分片 / 总片数」以前一直是 0/0,现在正确显示
- 最后一个分片完成后状态不再停留在未完成
- 已完成的任务也能查看分片明细
- 修好 socket 路径过长时 daemon 起不来:路径超过系统上限自动改用短路径
- 打包时校验应用图标,没打进 bundle 直接让构建失败
v2.0.4
- 详情页分片表可以展开看全部,默认仍只列前 64 个
- 换任务时自动收起
v2.0.3
- 修好应用图标:之前打包没传图标参数,访达和 Dock 里显示的是打包工具的默认图标
- macOS 菜单栏常驻图标,菜单项跟桌面版一致:打开主窗口、加 URL、在访达显示下载目录、关于、设置、退出
- Windows 也用同一个图标
v2.0.2
- 一个包就够:命令行和后台守护都装在 app 里,首次启动自动装好
- macOS 把
sdm软链到 PATH 并注册开机自启 - Windows 首次运行时释放命令行和守护进程,加进 PATH 并注册计划任务
- macOS DMG 生成(
build.py --mac)
v2.0.1
Python 重写版。跨 macOS、Windows、Linux,界面换成液态玻璃。
- 引擎改用 httpx 真流式传输,字节到达即落盘
- 分片下载默认 32 段,上限 128
- 断点续传按已落盘字节继续
- 链接过期(403/410/451)时任务转「等待新链接」,换新 URL 后从已完成分段续传
- 全局限速、单任务限速、时间段限速规则
- 自动文件分类,无扩展名文件按 magic byte 识别
- 站点规则支持 Referer、User-Agent、Cookie、Basic Auth
- 下载历史、计划任务、站点规则、日志、通知
- 剪贴板候选确认、浏览器扩展派单
sdm命令行,每个命令返回 JSON- 后台 daemon,开机自启
- 窗口自由缩放,布局重排不裁切
- 详情页显示真实分片状态
- macOS DMG 生成(
build.py --mac)
Windows 22H2 与 macOS 12+。