跳转至

downctl 设计文档

一个基于插件架构的通用命令行文件下载器。 具备断点续传、多连接加速、断线自愈重试等能力,一切皆插件。

  • 项目名:downctl
  • 命令:downctl
  • 仓库:https://github.com/ThzxxArt/downctl
  • 作者:ThzxxArt
  • 许可证:MIT
  • 最低 Python:3.11

1. 定位

downctl 是一个「通用」文件下载器:给定任意可下载目标,尽可能可靠、可续传、 可加速地把数据落到本地。它不绑定任何单一站点——对 HuggingFace 的支持只是 内置的一个「源插件」,与第三方插件走完全相同的接口。

核心理念:一切皆插件。


2. 核心理念的务实边界

「一切皆插件」必须区分两类东西,否则会把有状态的紧耦合算法拆得又慢又脆:

2.1 插件层(可变、可替换)

所有「策略型」能力都通过统一插件接口实现,内置功能也使用同一套接口 (dogfooding),第三方可以通过 pip 安装包替换或扩展:

类型 职责
Source 把用户输入解析成下载任务列表
Transport 实际的数据传输
Verifier 下载后校验
Sink 数据落地
Reporter 进度与日志呈现
Router URL 改写 / 镜像切换
Hook 生命周期事件回调

2.2 内核层(稳定、不插件化)

以下能力是有状态的紧耦合状态机,拆成插件只会带来性能损耗与脆弱性, 因此作为稳定的 Core API 暴露给插件调用,本身不是插件:

  • 分片调度与并发控制
  • 断点续传位图(piece bitmap)
  • 卡死看门狗(stall watchdog)
  • 限速(rate limit)
  • 单文件 / 单实例锁
  • 磁盘空间预检

一句话:内核是引擎,插件是齿轮;不是把引擎也拆成齿轮。


3. 目录结构

downctl/
├── src/downctl/
│   ├── __init__.py              # __version__、公共 API、API_VERSION
│   ├── __main__.py              # python -m downctl
│   ├── cli.py                   # argparse 子命令 + 分发
│   ├── app.py                   # 应用编排(加载插件→建任务→跑引擎)
│   ├── config.py                # TOML 配置 + 插件命名空间
│   ├── probe.py                 # 大小/状态探测
│   ├── platform_utils.py        # WSL/VT 颜色/安全 IO/pid 检活
│   ├── plugin/
│   │   ├── __init__.py
│   │   ├── protocols.py         # 七类插件 Protocol 定义
│   │   ├── registry.py          # 注册表 + 选择/优先级
│   │   ├── loader.py            # entry_points + 本地目录发现
│   │   └── hooks.py             # 生命周期事件总线
│   ├── core/                    # ★ 稳定内核(不插件化)
│   │   ├── __init__.py
│   │   ├── engine.py            # 调度 + 多次重试编排
│   │   ├── transfer.py          # 分片下载
│   │   ├── pieces.py            # 分片位图
│   │   ├── watchdog.py          # StallTimeout
│   │   ├── ratelimit.py         # 令牌桶限速
│   │   ├── locks.py             # 目录锁
│   │   ├── task.py              # DownloadTask 数据模型
│   │   ├── context.py           # RuntimeContext(注入 session/配置/事件)
│   │   └── errors.py            # 异常定义
│   └── builtin/                 # 内置插件(全部走插件接口)
│       ├── __init__.py
│       ├── sources/{url, huggingface, github, s3, sftp, file}_source.py, _sigv4.py
│       ├── transports/{http, sftp, file}_transport.py, _sftp.py
│       ├── verifiers/{sha256_verifier.py}
│       ├── sinks/{filesystem_sink.py}
│       ├── reporters/{tqdm, json, quiet}_reporter.py
│       ├── routers/{mirror_router.py}
│       └── hooks/{logfile, archive, webhook, manifest}_hook.py
├── tests/{unit/, integration/}
├── docs/
├── examples/plugins/            # 第三方插件示例
├── .github/workflows/{ci.yml, docs.yml, release.yml, codeql.yml}
├── mkdocs.yml
├── pyproject.toml
├── README.md / README.en.md
├── CHANGELOG.md / CONTRIBUTING.md / SECURITY.md / RELEASING.md / CODE_OF_CONDUCT.md
└── LICENSE

4. 插件体系

4.1 七类插件

类型 entry-point group 内置实现
Source downctl.sources url、huggingface、github、s3、sftp、file
Transport downctl.transports http、sftp、file
Verifier downctl.verifiers sha256
Sink downctl.sinks filesystem
Reporter downctl.reporters tqdm、json、quiet
Router downctl.routers mirror
Hook downctl.hooks logfile、archive、webhook、manifest、exec、checksum

4.2 发现机制(优先级从高到低)

  1. 本地目录:./downctl_plugins/ 与 ~/.config/downctl/plugins/
  2. pip 包:通过 importlib.metadata 读取 entry-points
  3. 内置:downctl.builtin

4.3 选择机制

  • 按 URI scheme 路由:https://→http,hf://org/repo→huggingface, gh://owner/repo→github,s3://bucket/key→s3,sftp://host/path→sftp, file:///path→file。
  • 可用 --source 强制指定。
  • 同类多插件按 priority + can_handle(target) 判定。

4.4 API 版本化

downctl.API_VERSION = "1"。每个插件声明其兼容的 api_version; 加载时不匹配则拒绝并给出清晰提示。

4.5 插件协议(示意)

class Source(Protocol):
    name: str
    priority: int
    api_version: str

    def can_handle(self, target: str) -> bool: ...
    async def resolve(self, target: str, ctx: "RuntimeContext") -> list["DownloadTask"]: ...

内核通过 RuntimeContext 向插件注入:HTTP 会话、配置、事件总线、 取消信号等运行时依赖。


5. CLI 设计

downctl get <target> [<target>...]    # 下载,按 scheme 自动路由源插件
    -d/--dir            保存目录
    -o/--output         保存为指定完整路径(单文件)
    -j/--jobs           并发文件数
    --piece-size        分片大小(如 8M)
    --min-split-size    小于该大小走单连接
    -t/--limit-rate     限速,如 5M / 1m / 512k
    -r/--retries        最大重试次数(0=无限)
    --force/--skip-existing  强制重下 / 跳过已存在
    --checksum          sha256:<hex>
    --proxy             代理地址
    --mirror FROM=TO    镜像改写
    --source <name>     强制指定源插件
    --reporter <name>   指定进度呈现插件
    --dry-run           只探测不下载
    --retry-failed      仅重试上次失败项
    -l/--list FILE      从文件/标准输入读取目标(- = stdin)

downctl probe <target> [--json]       # 只探测大小/状态,不下载
downctl plugin list|info <name>       # 插件管理
downctl config init|show|path         # 配置管理
downctl doctor                        # 环境诊断(代理/WSL/依赖/插件)
downctl wizard                        # 交互引导
downctl completions bash|zsh|fish     # 输出补全脚本
downctl version

示例:

downctl get https://example.com/big.iso -d ~/Downloads -t 5M
downctl get hf://meta-llama/Llama-3-8B --include '*.safetensors' -j 2

6. 配置

路径:~/.config/downctl/config.toml(可用 DOWNCTL_CONFIG 覆盖)。 支持环境变量插值 ${VAR}。

[core]
dir = "~/Downloads"
jobs = 2
retries = 0

[plugins.huggingface]
token = "${HF_TOKEN}"
mirror = "https://hf-mirror.com"

[plugins.tqdm]
enabled = true

7. 打包与发布

  • 构建后端:hatchling(PEP 621)
  • 依赖:aiohttp、tqdm;requires-python = ">=3.11"
  • 入口:[project.scripts] downctl = "downctl.cli:main"
  • 内置插件:同样声明为 entry-points(便于被覆盖/替换)
  • PyPI 发布:GitHub Actions + pypa/gh-action-pypi-publish, 使用 Trusted Publishing(OIDC,免 API token),绑定 ThzxxArt/downctl;push tag v* 触发。

8. CI/CD

  • ci.yml:lint(ruff/mypy)+ 矩阵测试 ubuntu/macos/windows × py3.11/3.12/3.13 (pytest --cov --cov-fail-under=85)+ build(构建并 twine check)+ audit(pip-audit 依赖漏洞扫描)。
  • release.yml:tag v* 触发,构建并发布到 PyPI,生成 GitHub Release。
  • docs.yml:构建 mkdocs 站点并部署到 GitHub Pages(Actions 部署,无 gh-pages 分支)。
  • codeql.yml:安全扫描。

9. 分阶段路线图

  1. P1 骨架:仓库 + pyproject.toml + 包结构 + 空 CI。
  2. P2 内核:task/pieces/transfer/watchdog/ratelimit/locks + 单元测试。
  3. P3 插件框架:Protocol + registry + loader + hooks + 发现测试。
  4. P4 内置插件:url/http/sha256/filesystem/tqdm/mirror + huggingface 源。
  5. P5 CLI/配置:子命令、TOML 配置、doctor/wizard。
  6. P6 文档:中英 README + 插件开发指南。
  7. P7 发布:PyPI(v0.1.0 已发布)。
  8. P8 扩展(可选):SFTP 细化、rich reporter、Homebrew、独立可执行文件。
  9. v0.1.2 打磨:续传感知进度、位图可重切、--force/--skip-existing、 标准输入、--piece-size/--min-split-size、probe --json、completions、 exec/checksum 钩子、GitHub 私有资源、更丰富的 -v 输出。

10. 风险与对策

  1. 插件抽象过早固化:API v1 发布后难以更改。对策——P3 阶段用 2-3 个真实 源(url / hf / 本地)打磨协议后再冻结。
  2. 性能开销:每文件过插件链。对策——热路径(分片循环)只调内核, 不穿越插件。
  3. 加载安全:本地目录插件等价于任意代码执行。对策——README 明确警告, 默认只自动信任 entry-points 插件。
  4. 测试成本高:内核状态机 bug 最多(续传/位图/看门狗)。对策——P2 测试先行,覆盖断线、Range、乱序续传、看门狗等分支。