v0.3.0
XferRust v0.3.0 Release Notes
English | 简体中文
Summary
- New disk cache
disk-cache(piece-level write-back buffering), magnet save/load as torrent (bt-save-metadata/bt-load-saved-metadata) - Peer-discovery switches now really take effect:
enable-dht/enable-dht6(BEP 32, DHT over IPv6 dual-stack) /enable-peer-exchange(BEP 11) - HTTP client configuration takes effect:
continueresume switch,user-agent,all-proxy/no-proxyproxy (client rebuilt on change) - New task file-verification RPC
task.verifyFiles(existence / size / streaming hash, executed natively by the engine) - UDP hole punching (
ut_holepunch) aligned with the libtorrent standard — relays through double NATs with standard clients such as qBittorrent - HTTP piece bitmaps, global & per-task rate limiting, per-task average speed, command-line option passthrough
- File selection (
select-file) works end-to-end;.torrentadditions support thebt-file-selectionflow - Piece display upgrades: persisted tri-state bitmap (
partialBitfield), wanted-piece bitmapwantedBitfield, seed durationbt-seed-timethat stops seeding automatically when reached; BT peer IP banning andtask.getPeersper-peer stats / banned group - Fixed phantom progress on unselected files and task progress above 100%: per-file and task progress now share one piece-segment accounting
- Fixed magnet/torrent tasks skipping "awaiting file selection" and downloading directly, garbled Chinese task names, inaccurate average speed
- All non-terminal tasks are restored as paused after restart; per-task rate limits now override the global one
New Features
Disk Cache (disk-cache)
- New global option
disk-cache(bytes or K/M/G suffix such as128M;0= write-through off): BT piece data is first buffered in a memory write-back queue and flushed to disk in FIFO order, reducing small random writes - Read paths serve cached pieces directly from memory without touching disk;
flush_allflushes the cache before syncing files, so the resume bitmap is only persisted after its data is on disk - Pieces larger than the cache limit degrade to direct writes; rewriting the same piece replaces the old entry without double counting; invalid values degrade to a warning without aborting the batch
Magnet Save / Load as Torrent (bt-save-metadata / bt-load-saved-metadata)
- New global option
bt-save-metadata: when magnet metadata arrives it is written to<download-dir>/<hex-infohash>.torrent(top-level dictionary hand-encoded with the original info bytes so the info_hash never shifts; atomic write via temp file + rename) - New global option
bt-load-saved-metadata: on magnet task start, if a same-named.torrentexists in the download dir with a matching info_hash, the metadata is installed directly, skipping the peer fetch (magnet becomes a torrent task instantly) - Tasks created from a
.torrentfile are not re-saved (the original file already exists)
Peer-Discovery Switches (enable-dht / enable-dht6 / enable-peer-exchange)
- New global option
enable-dht(DHT, BEP 5): on by default; when off, BT tasks no longer join the DHT; private torrents never use DHT (the constraint wins) - New global option
enable-dht6(DHT over IPv6, BEP 32): when on, DHT binds a dual-stack socket ([::]), bootstraps IPv6 nodes, replies to IPv6 requesters withnodes6/peers6and parses remote v6 nodes; if the machine has no IPv6 stack and binding fails, it automatically falls back to IPv4 - New global option
enable-peer-exchange(PEX, BEP 11): on by default; when off the extension handshake does not advertiseut_pexand PEX messages are neither sent nor received
HTTP Client Configuration (continue / user-agent / all-proxy / no-proxy)
- New global option
continue(HTTP resume switch, default on): when off, downloads always start from scratch even if the server supports Range; an existing file is kept and a new name is used (aria2 semantics) - New global options
user-agent/all-proxy/no-proxy: the HTTP client is built from them (UA overrides the engine default, proxy is direct or viaall-proxy); changing them rebuilds the client so new connections pick it up immediately - Desktop proxy settings (system / custom) and UA settings now really apply to download traffic
Task BT Identity (bittorrent / infoHash)
- Task status responses (
task.tell/task.list, both the aria2-style and native numeric encodings) now carry abittorrentobject and aninfoHashfield - Non-BT tasks report
bittorrentasnull; when metadata is ready it is{"info": {"name", "hash"}}; while magnet metadata is being fetched it is{}(the frontend shows "fetching metadata") infoHashis the hex encoding of the info dictionary hash: .torrent tasks report the hash computed at parse time, magnet tasks report thebt_info_hashfrom the handshake / metadata exchange
Global Tracker List & Subscription Switch Passthrough
engine.changeOptionsgainsbt-trackers: a string array or newline/comma-separated string with full-replacement semantics, synchronized to all active BT tasks with the same delta semantics as manual add/remove- New switch
auto-update-trackerscontrols tracker subscription auto-update ("false"/"0"both mean off)
Command-Line Runtime Option Passthrough
- The engine command line accepts
--key=valueruntime global options injected into the same store asengine.changeOptions(CLI values override session-restored ones):split,max-connection-per-server,min-split-size, overall rate limits,bt-max-peers,bt-adaptive,bt-seed-mode,bt-seed-ratio,bt-encryption,bt-protocol,bt-listen-port,dht-listen-port,bt-enable-lpd,bt-port-mapping - App-side toggles are auto-mapped:
--enable-upnp/--enable-nat-pmp→bt-port-mapping,--enable-utp→bt-protocol - Any unknown
--key=valueis accepted as a global default (see Behavior Changes), so host applications can pass their entire configuration through the command line
Piece Display, Real-Time Speed & Peer Management
- Download speed now uses a 3-second sliding window refreshed every second: the window average smooths piece-level write jitter while the value updates every second
- BT piece bitfield exposure:
bitfieldreports the real "downloaded pieces" bitmap (aria2-compatible hex encoding), synced at 1 Hz, preserving the last known state while paused - Per-peer bitfield: each peer in
task.getPeersgains abitfieldfield (pieces the peer owns, all ones for seeds) - BT peer IP banning:
task.banPeer(auto-unbans afterdurationseconds,<= 0permanent; immediately disconnects existing connections) /task.unbanPeer; the ban list persists across sessions - Global ban-list option:
engine.changeOptionsgainsbt-ip-ban-list(IP array or newline/comma-separated string, full replacement of permanent bans) - New
task.changeUri(aria2-compatible: removesdelUris/ appendsaddUrisperfileIndexfor waiting/paused tasks, rejected when active) andtask.getServers(HTTP tasks return server entries, BT returns an empty array)
Per-Task Average Speed (averageSpeed)
- Task status responses gain an
averageSpeedfield (bytes/sec): accumulated every second during active download phases; seeding and paused phases neither accumulate nor dilute it - The accumulated data persists across sessions, so the average does not drift after restart-and-resume; the app reads this field directly instead of sampling
File Selection (select-file) End-to-End
task.changeOptionnow actually appliesselect-file(aria2 semantics: comma-separated 1-based file indices, empty = select all): running BT tasks hot-apply it, paused/waiting tasks pick it up on their next startfiles[].selectedreports the real selection: the native encoding previously hardcodedtrue; the aria2 encoding now outputs"true"/"false"- File selection extends to HTTP/HTTPS tasks: single-file layout, persisted and applied on the next start
task.addacceptsselect-filefor pre-selection; invalid/out-of-range values degrade to a warning without aborting the addition.torrentadditions support thebt-file-selectionflow: the same state machine as magnets — auto-pause once metadata is ready, resume viaselect-fileafter the client confirms the selection- Single-file magnets auto-resume: when the layout is a single file, the engine resumes at full selection without user action
HTTP Piece Bitmap & Global Rate Limiting
- HTTP tasks report real piece data:
numPieces/pieceLength/bitfield(aria2-compatible hex encoding) - Piece length is a display granularity decoupled from the segment granularity:
min(min-split-size, max(total/2048, 64KB))— even a few-MB file lights up pieces promptly, large files keep themin-split-sizegranularity - The write side accounts bytes incrementally per landed range on both multi-connection and single-connection paths; the bitmap is rebuilt when a server ignores Range and resends the full body; control-file watermarks pre-fill the bitmap on resume so it stays consistent with disk; omitted when total length is unknown or Range is unsupported
- HTTP global rate limiting: an asynchronous token bucket is injected into every download connection, letting TCP backpressure converge naturally. Previously HTTP downloads were entirely unthrottled
- Speed-limit value parsing upgraded: overall limits accept aria2-style units (
1M/500K/ plain bytes), previously only plain integers - One invalid key no longer aborts the whole settings batch: illegal values in
changeOptionsdegrade to a warning and skip that key
Per-Task Rate Limiting (max-download-limit / max-upload-limit)
task.changeOptionacceptsmax-download-limit/max-upload-limit(aria2 semantics,1M/500Kunits pass through), covering HTTP and BT, hot-reloaded and persisted across sessions- The effective value follows per-task-override semantics (see Behavior Changes);
task.getOptionreports the current per-task limits - Each HTTP task owns a limiter shared by the single-connection and split paths; BT engines receive the composed values directly
Tracker Announce State
task.getTrackersis upgraded from URL-only entries to per-tracker state:protocol(http/https/udp/ws),status(working/not-working/waiting),seeders/leechers,peers,lastAnnounceTime/nextAnnounceTime,error- The BT engine records per-URL results during each announce round; URLs not yet announced stay
waiting
Piece Tri-State Display & Persistence
- Task status responses gain
partialBitfield: HTTP tasks report a partial-download piece bitmap (pieces with bytes landed > 0 but not complete), BT tasks always report an empty string; combined withbitfieldthe UI can render not-started / in-progress / completed tri-state maps - Session serialization saves
btBitfield,httpNumPieces/httpPieceLen; restoration rebuilds the bitmaps and backfills piece state from completed bytes — previously completed tasks lost their piece maps after restart
Task File Verification (task.verifyFiles)
- New native RPC
task.verifyFiles: runs existence checks, file size comparison, and streaming hash computation (algorithmacceptssize/sha256/sha1/md5/sha512, case-insensitive) - Path resolution and disk reads happen entirely in the engine: BT multi-file entries are joined as "name/relative-path", HTTP tasks use the actual on-disk path; unselected BT files are excluded
- Returns a structured result:
status(ok/missing/sizeMismatch),count,missing/mismatchedandhashes(path+ hexdigest, empty forsizeverification) engine.getVersion'sfeaturesadds"verify-files";xfer-storageaddsfile_digest_hexsharing the underlying implementation withverify_file_hash
UDP Hole Punching Standardization (ut_holepunch, libtorrent de-facto standard)
- Wire format aligned with libtorrent:
msg_type(1) + addr_type(1) + addr(4/16) + port(2), with onlyfailedappending a 4-byte error code; message types and error codes exactly matchbt_peer_connection. The previous private format could not be parsed by any standard client - Relay (rendezvous) semantics aligned: resolve the target connection first (exact endpoint match + same-IP fallback), replying the appropriate
failedwhen unreachable / unsupported / self-targeted; on success both sides receive a connect - Initiator side added: once direct-dial retries are exhausted, the engine asks any connected peer advertising
ut_holepunchto relay (capped at 2 rounds per target, 2 relays per round); previously it only ever answered relay requests, making double-NAT traversal with standard clients impossible - Holepunch messages from peers that did not advertise
ut_holepunchare ignored; PEXadded.fflags corrected to libtorrent semantics (0x08 = holepunch-capable, 0x04 = uTP)
Peer Info Extensions & Ban Display (task.getPeers)
- Each peer gains dial / transport stats fields:
downSpeed/upSpeed/tcpFails/utpFails/udpFails/attempting - New banned group: bans are recorded per IP (
addrholds only the address,portempty) and reportremainingSecs(0 = permanent),sourceandbanReason(manual= banned by hand /ban_list= pushed via the ban list); onegetPeerscall returns all four groups — connected / attempting / disconnected / banned - Ban entries distinguish their origin:
task.banPeeris recorded as manual,bt-ip-ban-listpushes as list entries; persisted across sessions, with legacy session files defaulting to list entries
BT Seed Duration (bt-seed-time)
- New global option
bt-seed-time(minutes, 0 = unlimited): seeding stops automatically and the task turns complete once the duration is reached — a second seeding exit condition alongsidebt-seed-ratio - Changes are pushed hot to all active BT engines: seeding tasks re-evaluate against the new duration (the seeding start time is unchanged), and tasks finishing download later pick it up when they enter seeding; persisted with the session
Wanted-Piece Bitmap (wantedBitfield)
- Task status responses gain
wantedBitfield(aria2-compatible hex encoding, same format asbitfield): for BT tasks with a partial file selection it reports the pieces that must be downloaded; it is an empty string for full / no selection - Pieces belonging only to unselected files are never set (the engine neither requests nor writes them), so the UI can tell "not selected, not needed" apart from "not downloaded" — previously a completed task still showed a few grey cells at the end that looked like missing pieces
- Pieces spanning a selected/unselected boundary still count as wanted (pieces cannot be split; the whole piece is downloaded while the unselected side is not written)
Charset-Aware Text Decoding (xfer-types::text)
- New text decoding module: explicit charset → strict UTF-8 → GB18030 → lossy, used uniformly by magnet / .torrent / HTTP parsing
- Magnet
dnpercent-encoding, .torrentnameand path segments, and HTTPContent-Disposition: filename*(RFC 5987, honoring declared gb2312/gbk charsets) and URL-path percent-encoding all go through this module
Bug Fixes
- Fix misreported per-file progress on BT tasks:
files[].completedLengthused to be estimated as "file length × overall progress / total length of all files", so a file the user never selected still showed progress (observed as 80% / 11.4 MB). It is now accounted per piece boundary segment — each file only accumulates the bytes of completed pieces that fall inside it, unselected files stay at 0, and the per-file values sum up consistently with the task progress - Fix task progress exceeding 100% (observed as 100.08%): with a partial file selection the total shrinks to the selected files' length while completed bytes were summed as whole piece lengths of wanted pieces, pulling the unselected side of boundary pieces into the numerator. Bytes are now attributed per segment, so
completedLengthnever exceedstotalLengthand a finished task reads exactly 100% - Fix magnet/.torrent tasks skipping "awaiting file selection" and downloading directly:
task.addpreviously passed through onlydir/out/checksum, silently dropping task-level options such asbt-file-selection/select-file; all options except reserved protocol keys are now passed through, so the auto-pause-awaiting-selection flow works again - Fix inaccurate task average speed: the formula
bytes/(ms/1000)inflated the average when active time was under 2 seconds due to integer truncation (nearly 2x error at ms=1999, inflated early readings); it now computesbytes*1000/ms. Speed sampling also starts at the moment the task starts, so the first 1 Hz tick's bytes count toward the average (previously dropped, systematically underreporting short tasks) - Fix the "awaiting file selection" state appearing late after magnet metadata is ready: the metadata-fetch loop's tracker announce (which can block up to a 15s timeout) and its 1-second polling were not cancellation-aware, deferring the pause intent until the round finished; cancellation is now handled first and the pause takes effect immediately
- Fix garbled Chinese task/file names (displayed as a run of
????): Chinese sites commonly percent-encode the magnetdnin GBK, old Chinese torrents storenameand path segments as GBK bytes (previously rejected outright as "info missing name"), and HTTPfilename*headers declaring gb2312/gbk were decoded as UTF-8 — all now go through charset-aware decoding, see "Charset-Aware Text Decoding" - Fix completed tasks losing piece progress after restart: piece bitmaps were not saved or restored across sessions, see "Piece Tri-State Display & Persistence"
- Fix saved file selections not taking effect and the detail view reopening with "none selected":
select-filewas previously only stored, never applied, see "File Selection (select-file) End-to-End" - Fix HTTP piece bitmaps staying all-zero for files of just a few MB: the piece length was previously fixed to
min-split-size, see "HTTP Piece Bitmap & Global Rate Limiting" - Fix HTTP tasks falsely reporting
seeder=trueon completion:seedernow means "this endpoint is a BT task and its payload is complete"; it used to be computed as "completed ≥ total" for every task type, making the client mark normal downloads as "seeding" - Fix speed-limit settings not taking effect: unit-suffixed values were rejected or silently treated as unlimited, and the HTTP download path had no rate-limit enforcement at all, see "HTTP Piece Bitmap & Global Rate Limiting"
Behavior Changes
- Per-task rate limits now override the global one: a set task limit takes precedence and may be higher or lower than the global value; unset (0) follows global. Previously the effective value was min(per-task, global), so a task limit above the global one was clamped back
- Session restore is tightened: all non-terminal tasks (active / waiting / paused) are restored as paused and no longer auto-start; resume data is saved with the session, so a manual resume continues from the previous progress
- Unknown engine command-line options are no longer "warned and dropped": any
--key=valueis accepted as a global default, readable viaengine.getOptionsand persisted with the session; only bare positional arguments are treated as invalid
Build & Release
- CI build matrix gains
linux-arm64(aarch64-unknown-linux-musl static linking — no glibc dependency, no bundled lib/ directory), cross-compiled with cargo-zigbuild - macOS engine-core artifacts are split per architecture: the TUI remains a universal dual-architecture binary, while the engine core ships as
xferrust-darwin-aarch64.tar.gzandxferrust-darwin-x86_64.tar.gz— embedding clients fetch the matching artifact directly, no thin extraction needed
XferRust v0.3.0 发布说明
English | 简体中文
摘要
- 新增磁盘缓存
disk-cache(片级写回缓冲)、磁力保存 / 加载为种子(bt-save-metadata/bt-load-saved-metadata) - 网络发现开关真实生效:
enable-dht/enable-dht6(BEP 32,DHT over IPv6 双栈)/enable-peer-exchange(BEP 11) - HTTP 客户端配置生效:
continue断点续传开关、user-agent、all-proxy/no-proxy代理(变更即重建客户端) - 新增任务文件校验 RPC
task.verifyFiles(存在性 / 大小 / 流式哈希,引擎原生执行) - UDP 打洞
ut_holepunch对齐 libtorrent 标准,可与 qBittorrent 等标准客户端互为中介穿透双 NAT - HTTP 分片位图、全局限速与任务级限速、任务平均速度、命令行选项直通
- 文件选择
select-file全链路生效,.torrent支持bt-file-selection勾选流程 - 分片展示升级:三态位图(
partialBitfield)持久化、需下载片位图wantedBitfield、做种时长bt-seed-time到时自动停止;BT 对端 IP 封禁与task.getPeers对端统计 / 已封禁分组 - 修复未勾选文件虚报进度、任务进度超过 100%:文件进度与任务进度统一按片段长归属统计
- 修复磁力 / 种子任务跳过「待选择文件」直接下载、中文任务名乱码、平均速度计算不准
- 重启后所有未完成任务恢复为暂停;单任务限速优先覆盖全局
新特性
磁盘缓存(disk-cache)
- 新增全局选项
disk-cache(字节数或 K/M/G 后缀,如128M;0 = 关闭直写):BT 片数据先聚合在内存写回缓冲,按 FIFO 逐出落盘,减少随机小写次数 - 读路径对缓存命中的片直接返回内存数据,不访问磁盘;
flush_all先落缓存再刷盘,保证续传位图落盘时对应数据必已在磁盘 - 片大于缓存上限时自动退化为直写;同一片重复写入替换旧条目不重复计数;非法值告警跳过不中断整批设置
磁力保存 / 加载为种子(bt-save-metadata / bt-load-saved-metadata)
- 新增全局选项
bt-save-metadata:磁力元数据到手即写<下载目录>/<16 进制 infohash>.torrent(手工 bencode 拼接 info 原始字节,info_hash 不因重编码错位;原子写临时文件 + rename) - 新增全局选项
bt-load-saved-metadata:磁力任务启动时若下载目录存在同名.torrent且 info_hash 匹配,直接安装元数据免去从对端重新拉取(磁力任务秒变种子任务) - 从
.torrent文件创建的任务不重复保存(原始文件已在)
网络发现开关(enable-dht / enable-dht6 / enable-peer-exchange)
- 新增全局选项
enable-dht(DHT,BEP 5):默认开,关闭后 BT 任务不再参与 DHT;private 种子恒不使用 DHT(约束优先) - 新增全局选项
enable-dht6(DHT over IPv6,BEP 32):开启时 DHT 绑定双栈 socket([::])、bootstrap 含 IPv6 节点、向 IPv6 请求方回nodes6/peers6并解析对端 v6 节点;机器无 IPv6 栈导致绑定失败时自动回退纯 IPv4 - 新增全局选项
enable-peer-exchange(PEX,BEP 11):默认开,关闭后扩展握手不声明ut_pex、不发送也不接收 PEX 消息
HTTP 客户端配置(continue / user-agent / all-proxy / no-proxy)
- 新增全局选项
continue(断点续传开关,默认开):关闭后即使服务器支持 Range 也强制从头下载,已有文件按「重命名新文件」处理(aria2 语义) - 新增全局选项
user-agent/all-proxy/no-proxy:HTTP 客户端按配置构建(UA 覆盖引擎默认值、代理直连或经all-proxy转发);运行中变更即重建客户端,新连接立即生效 - 桌面端代理设置(系统 / 自定义)与 UA 设置现在真实作用于下载流量
任务状态 BT 标识(bittorrent / infoHash)
- 任务状态响应(
task.tell/task.list,aria2 风格与原生数值两种编码)新增bittorrent对象与infoHash字段 - 非 BT 任务
bittorrent为null;元数据就绪为{"info": {"name", "hash"}};磁力元数据获取中为{}(前端据此显示「获取元数据中」) infoHash为 info 字典哈希的十六进制表示:.torrent 任务取解析时计算的哈希,磁力任务取握手 / 元数据交换得到的bt_info_hash
全局 tracker 与订阅开关直通
engine.changeOptions新增bt-trackers:字符串数组或换行 / 逗号分隔字符串,全量替换语义,与手动增删相同的增量语义同步到所有活动 BT 任务- 新增开关
auto-update-trackers控制订阅源自动更新("false"/"0"均视为关闭)
命令行运行时选项直通
- 引擎命令行支持
--key=value直通注入全局选项(与engine.changeOptions同一存储,CLI 传值覆盖会话恢复旧值):split、max-connection-per-server、min-split-size、全局限速、bt-max-peers、bt-adaptive、bt-seed-mode、bt-seed-ratio、bt-encryption、bt-protocol、bt-listen-port、dht-listen-port、bt-enable-lpd、bt-port-mapping - 应用侧开关自动映射:
--enable-upnp/--enable-nat-pmp→bt-port-mapping,--enable-utp→bt-protocol - 任意未知
--key=value宽容接受为全局默认值(详见行为变更),宿主应用可透传完整配置,引擎升级后无需改动启动参数
分片展示、速度实时化与对端管理
- 下载速度改为每秒刷新的 3 秒滑动窗口:窗口平均抹平片级批量落盘的抖动,速度值每秒更新,不再长时间纹丝不动
- BT 分片位图暴露:
bitfield输出真实「已下载片」位图(aria2 兼容 hex 编码),驱动侧 1Hz 同步,暂停后保留最后已知状态 - 逐对端位图:
task.getPeers每个对端新增bitfield字段(对端已拥有片,seed 为全 1) - BT 对端 IP 封禁:
task.banPeer(duration秒后自动解封,<= 0永久;立即断开现有连接)/task.unbanPeer;封禁名单随会话持久化 - 全局封禁名单选项:
engine.changeOptions新增bt-ip-ban-list(IP 数组或换行 / 逗号分隔字符串,全量替换永久封禁) - 新增
task.changeUri(aria2 兼容:waiting/paused 下按fileIndex删delUris追加addUris,active 拒绝)与task.getServers(HTTP 任务返回服务器条目,BT 返回空数组)
任务平均速度(averageSpeed)
- 任务状态响应新增
averageSpeed字段(字节/秒):活动下载阶段逐秒累计「字节增量 + 时长」,做种与暂停阶段不累计不稀释 - 累计数据随会话持久化,重启续传后均值不漂移;应用端直取该字段刷新,无需自行采样
文件选择(select-file)全链路
task.changeOption真正应用select-file(aria2 语义:1 起算的逗号分隔文件序号,空 = 全选):BT 运行中热生效,暂停 / 等待任务下次启动生效files[].selected按真实选择上报:原生编码此前硬编码true,aria2 编码输出"true"/"false"- 文件选择扩展到 HTTP/HTTPS 任务:单文件布局,选择状态持久化、下次启动生效
task.add支持select-file预选;无效 / 越界取值降级为告警,不中断添加.torrent添加支持bt-file-selection勾选流程:与磁力同一状态机——元数据就绪后自动暂停,应用端勾选后以select-file恢复- 单文件磁力自动续下:元数据就绪后若为单文件布局,自动按全量选择续下,无需手动恢复
HTTP 分片位图与全局限速
- HTTP 任务输出真实分片数据:
numPieces/pieceLength/bitfield(aria2 兼容 hex 编码) - 片长 = 进度显示粒度,与分段粒度解耦:取
min(min-split-size, max(total/2048, 64KB))——几 MB 小文件也能及时点亮分片,大文件保持min-split-size粒度 - 写侧按落盘区间增量记账,多连接与单连接路径均覆盖;服务器无视 Range 重发全量时位图作废重建;控制文件水位预填保证重启后位图与落盘自洽;未知总长或不支持 Range 时不输出
- HTTP 全局限速执行:异步令牌桶注入每条下载连接,令牌不足时异步等待,TCP 背压自然收敛。此前 HTTP 下载完全不受限速约束
- 限速值解析升级:全局限速接受 aria2 风格单位(
1M/500K/ 纯整数字节),此前仅接受纯整数 - 单键错误不再中断整批设置:
changeOptions中非法取值降级为告警并跳过该键,其余设置照常生效
任务级限速(max-download-limit / max-upload-limit)
task.changeOption支持max-download-limit/max-upload-limit(aria2 语义,1M/500K单位直通),HTTP 与 BT 全覆盖,运行时热生效、随会话持久化- 生效值语义见行为变更:单任务优先覆盖全局;
task.getOption输出当前任务限速值 - HTTP 每任务持有独立限速器(单连接与 split 多连接共享),BT 由各 TorrentEngine 按合成值下发
tracker announce 状态
task.getTrackers从仅返回 URL 升级为带 per-tracker 状态:protocol(http/https/udp/ws)、status(working/not-working/waiting)、seeders/leechers、peers、lastAnnounceTime/nextAnnounceTime、error- BT 引擎在每轮 announce 聚合时逐 URL 记录,未 announce 过的 URL 保持 waiting
分片三态展示与持久化
- 任务状态响应新增
partialBitfield:HTTP 任务输出部分下载分片位图(已落盘 > 0 但未满的分片为 1),BT 任务恒为空串;配合bitfield可渲染未开始 / 下载中 / 已完成三态分片图 - 会话保存新增
btBitfield、httpNumPieces/httpPieceLen,重启恢复时重建位图并按已下载字节回填状态——此前重启后已完成任务分片图消失
BT 做种时长(bt-seed-time)
- 新增全局选项
bt-seed-time(分钟,0 = 不限时):做种达到时长后自动停止做种转为完成,与bt-seed-ratio并列的第二个做种退出条件 - 选项变更热下发到所有活动 BT 引擎:正在做种的任务按新时长计时(做种起始时刻不变),下载中的任务完成进入做种时生效;随会话持久化
需下载片位图(wantedBitfield)
- 任务状态响应新增
wantedBitfield(aria2 兼容 hex 编码,与bitfield同格式):BT 任务勾选部分文件时输出「需要下载的片」位图,全选 / 无选择时为空串 - 只属于未勾选文件的分片永远不会置位(引擎不下发也不落盘),配合该字段界面可把「未选择,无需下载」与「未下载」区分显示——此前任务已 100% 完成后末尾仍残留灰格,看起来像没下完
- 跨选 / 未选边界的片仍标记为需要下载(片不可拆分,边界片整体下载,未选一侧不落盘)
任务文件校验(task.verifyFiles)
- 新增原生 RPC
task.verifyFiles:对指定任务执行存在性检查、文件大小比对、流式哈希计算(algorithm支持size/sha256/sha1/md5/sha512,大小写不敏感) - 路径解析与读盘全部在引擎内完成:BT 多文件按「目录名/相对路径」拼接任务目录,HTTP 任务取实际落盘路径;未选择的 BT 文件不参与校验
- 返回结构化结果:
status(ok/missing/sizeMismatch)、count、missing/mismatched与hashes(path+digest十六进制摘要,size校验时为空) engine.getVersion的features新增"verify-files";xfer-storage新增file_digest_hex与已有verify_file_hash共享底层实现
UDP 打洞标准化(ut_holepunch,libtorrent 事实标准)
- 线格式对齐 libtorrent:
msg_type(1) + addr_type(1) + addr(4/16) + port(2),仅failed追加 4 字节错误码;消息类型与错误码枚举与bt_peer_connection完全一致。此前私有线格式无法被任何标准客户端解析 - 中介(rendezvous)语义对齐:先解析目标连接(精确 endpoint 匹配 + 同 IP 回退),不可达 / 不支持 / 目标为发起方分别回对应
failed;成功则双向转发 connect - 补齐发起侧:直连重试耗尽后,主动向已连接且广告
ut_holepunch的 peer 请求中介(每目标最多 2 轮、每轮最多 2 个中介);此前只会被动应答,双 NAT 下无法与标准客户端互相穿透 - 未在扩展握手广告
ut_holepunch的对端发来的消息一律忽略;PEXadded.f标志位修正为 libtorrent 语义(0x08 = 支持 ut_holepunch,0x04 = uTP)
对端信息扩展与封禁展示(task.getPeers)
- 每个对端新增拨号 / 传输统计字段:
downSpeed/upSpeed/tcpFails/utpFails/udpFails/attempting - 新增已封禁分组:封禁按 IP 记录(
addr仅地址、port空),输出remainingSecs(0 = 永久)、source与banReason(manual= 手动封禁 /ban_list= 名单下发);一次getPeers同时给出在线 / 尝试中 / 已断开 / 已封禁四类 - 封禁条目区分来源:
task.banPeer记为手动,bt-ip-ban-list下发记为名单;随会话持久化,旧会话文件无来源字段时视为名单下发
字符集探测解码(xfer-types::text)
- 新增文本解码模块:显式 charset → 严格 UTF-8 → GB18030 → lossy 逐级回退,统一供磁力 / .torrent / HTTP 解析使用
- 磁力
dn百分号编码、.torrentname与路径段、HTTPContent-Disposition: filename*(RFC 5987,尊重声明的 gb2312/gbk 字符集)与 URL 路径百分号编码均走该模块
问题修复
- 修复 BT 任务文件进度错报:
files[].completedLength曾按「文件长度 × 任务总进度 / 全部文件总长」估算,未勾选下载的文件也会显示进度(实测显示 80% / 11.4 MB)。现逐片按段长归属统计——每文件只累计「已完成片落在该文件内的字节」,未选文件恒为 0,各文件之和与任务进度自洽 - 修复任务进度超过 100%(实测 100.08%):勾选部分文件时总量收缩为所选文件长度,而已完成字节按「所需片整片片长」累计,跨选 / 未选边界片的未选一侧被一并计入分子。现按段长归属只计所选文件一侧,
completedLength恒不超过totalLength,完成任务恰好 100% - 修复磁力 / .torrent 任务跳过「待选择文件」直接开始下载:
task.add此前只透传dir/out/checksum,bt-file-selection/select-file等任务级选项被静默丢弃;现除协议保留键外全量透传,元数据就绪后照常自动暂停等待勾选 - 修复任务平均速度不准:均值公式
bytes/(ms/1000)在活动时长不足 2 秒时因整数截断被放大(ms=1999 误差近 2 倍,开头几秒虚高),改为bytes*1000/ms;速度采样同时改为从任务启动瞬间开始,首个 1Hz tick 的字节计入均值(此前被丢弃,短任务均值系统性偏低) - 修复磁力任务元数据就绪后「待选择文件」延迟出现:元数据获取循环内的 tracker announce(单次可阻塞到 15s 超时)与 1 秒轮询不感知取消,暂停意图要等本轮结束才落地;现取消优先处理,暂停即时生效
- 修复中文任务名 / 文件名乱码(显示为一串
????):中文站点磁力dn常用 GBK 百分号编码、老中文种子name与路径段为 GBK 字节(此前非 UTF-8 会被整包拒收)、HTTPfilename*声明 gb2312/gbk 时按 UTF-8 解码——均改走字符集探测解码,详见「字符集探测解码」 - 修复重启后已完成任务分片进度消失:分片位图此前不随会话保存与恢复,详见「分片三态展示与持久化」
- 修复「选择文件」保存后不生效且重开详情页全部显示未选:
select-file此前只被存储从未应用,详见「文件选择(select-file)全链路」 - 修复几 MB 小文件 HTTP 分片位图恒为零:片长曾固定取
min-split-size,详见「HTTP 分片位图与全局限速」 - 修复 HTTP 任务完成后误报
seeder=true:seeder语义修正为「本端为 BT 任务且已完整」,此前按「completed ≥ total」对所有任务类型计算,应用端据此把普通任务标成「做种中」 - 修复限速设置不生效:带单位的限速值此前被拒绝或静默按不限速处理,且 HTTP 下载路径完全没有限速执行,详见「HTTP 分片位图与全局限速」
行为变更
- 单任务限速生效语义调整为「单任务优先覆盖全局」:已设置的任务限速优先生效,可高于也可低于全局;未设置(0)跟随全局。此前为两者取小,单任务限速无法高于全局
- 重启恢复语义收紧:所有未完成任务(活动 / 等待 / 暂停)一律恢复为暂停,不再自动开始下载;断点数据随会话保存,手动恢复即续传
- 引擎命令行未知选项不再「告警丢弃」:任意
--key=value宽容接受为全局默认值,engine.getOptions可读回、随会话持久化,仅裸位置参数视为无效告警忽略
构建与发布
- CI 构建矩阵新增
linux-arm64(aarch64-unknown-linux-musl 静态链接,无 glibc 依赖、免带 lib/ 目录),交叉编译使用 cargo-zigbuild - macOS 引擎内核产物按架构拆分:TUI 仍为双架构通用二进制,引擎内核分为
xferrust-darwin-aarch64.tar.gz与xferrust-darwin-x86_64.tar.gz,嵌入端按目标架构直取,无需 thin 提取