Skip to content

feat(func): support ed2k & magnet & torrent offline download#2452

Open
PIKACHUIM wants to merge 3 commits intomainfrom
dev-cas
Open

feat(func): support ed2k & magnet & torrent offline download#2452
PIKACHUIM wants to merge 3 commits intomainfrom
dev-cas

Conversation

@PIKACHUIM
Copy link
Copy Markdown
Member

@PIKACHUIM PIKACHUIM commented May 7, 2026

Description / 描述

支持BT种子文件/ED2K/磁力链离线下载,支持天翼云CAS秒传

功能支持项

  1. 支持打开 torrent 发起离线任务,支持选择部分文件下载,支持选择保存路径
  2. 支持使用 ed2k://magnet:? 链接发起离线下载任务
  3. 支持驱动生成 torrent 文件(含 SHA-1 piece hash),实现分享种子以供其他用户秒传
  4. 特别的,对于天翼云,在种子中内嵌 CAS 信息,支持天翼云秒传
  5. 转存到天翼云时自动尝试秒传(计算 MD5 + sliceMD5),失败后回退普通上传
  6. 前端增强版离线下载对话框,支持 Tab 切换(链接下载/BT下载)、文件树形选择、CAS 秒传提示
  7. 种子/磁力链/ED2K 场景自动禁用 SimpleHttp 下载工具
  8. 有 CAS 信息且目标为天翼云时,默认直接走 CAS 秒传(不需要选择下载工具)

预期的逻辑

  1. 可以从列表中选择BT文件发起离线下载,要求支持只勾选部分文件,并且需要选择保存路径(默认当前路径)
  2. 可以支持使用 ed2k 和磁力链获取种子/发起离线下载任务,同上
  3. 可以从本地上传 bt 文件并发起离线任务,同上
  4. BT和磁力链ED2K,非天翼云,都要优先尝试秒传
  5. 对于天翼云,如果BT文件包含CAS,则秒传,否则下载到本地再秒传

后端改动

新增文件

文件 说明
pkg/torrent/bencode.go Bencode 编解码实现(BT 协议标准格式)
pkg/torrent/torrent.go Torrent 文件结构定义、编码/解码、x-cas 扩展支持
pkg/torrent/hash_writer.go HashWriter:同时计算 MD5(CAS秒传)和 SHA-1(BT piece hash)
pkg/torrent/generate.go 通用 torrent 生成函数(不绑定特定驱动)
drivers/189pc/torrent.go 189PC 驱动:torrent 生成、CAS 秒传(新版 initMultiUpload 接口)、CAS 注入/提取
drivers/189/torrent.go 189 驱动:torrent 生成、CAS 秒传
server/handles/torrent.go Torrent API 接口(解析/上传解析/秒传/生成)

修改文件

文件 说明
drivers/189pc/meta.go 添加 GenerateTorrent 配置选项
drivers/189pc/utils.go StreamUpload 中集成 SHA-1 计算,上传完成后异步生成并上传 torrent 文件
drivers/189/util.go newUpload 中集成 SHA-1 计算,上传完成后异步生成 torrent
internal/offline_download/tool/transfer.go 转存时自动尝试天翼云秒传 + 异步生成 torrent
server/router.go 注册 torrent 相关 API 路由

新增 API

方法 路径 功能
POST /api/fs/torrent/parse 解析 Base64 编码的 torrent,返回文件列表/CAS 信息
POST /api/fs/torrent/upload_parse 文件上传方式解析 torrent
POST /api/fs/torrent/rapid_upload 从 torrent CAS 信息秒传到天翼云(使用新版 initMultiUpload 接口)
POST /api/fs/torrent/generate 为指定路径文件生成 torrent

前端改动

新增文件

文件 说明
src/types/torrent.ts Torrent 相关 TypeScript 类型定义
src/pages/home/toolbar/OfflineDownloadEnhanced.tsx 增强版离线下载对话框(Tab切换/秒传/工具选择)
src/pages/home/toolbar/TorrentFileList.tsx Torrent 文件列表树形展示与选择组件

修改文件

文件 说明
src/types/index.ts 导出 torrent 类型
src/utils/api.ts 新增 torrentParse/torrentUploadParse/torrentRapidUpload API
src/utils/bus.ts 添加 torrent_parsed 事件类型
src/pages/home/toolbar/operations.ts 添加 offline_download_torrent 操作图标
src/pages/home/toolbar/Toolbar.tsx 替换为 OfflineDownloadEnhanced 组件
src/pages/home/folder/context-menu.tsx 为 .torrent 文件添加"离线下载"右键菜单
src/lang/en/home.json 添加离线下载增强相关 i18n 键值

BT文件逻辑

graph TD
    A[上传文件] --> B[按 10MB 分片]
    B --> C[每片计算 MD5<br/>用于天翼云秒传]
    B --> D[每片计算 SHA-1<br/>用于 BT piece hash]
    C --> E[fileMD5 + sliceMD5]
    D --> F[pieces 字段]
    E --> G[x-cas 扩展<br/>存储在 info 外部]
    F --> H[info 字典]
    G --> I[生成 .torrent 文件]
    H --> I
    
    J[收到 .torrent] --> K{有 x-cas?}
    K -->|是| L[提取 fileMD5 + sliceMD5]
    L --> M[调用秒传接口]
    M --> N[文件恢复成功]
    K -->|否| O[需要下载后计算 MD5]
    O --> P[注入 CAS 信息到 torrent]
Loading

离线下载完整流程

graph TD
    A[用户上传/选择 .torrent] --> B[解析 torrent]
    B --> C{有 CAS 信息?}
    C -->|是| D[直接 CAS 秒传<br/>不需要选择下载工具]
    D --> E{秒传成功?}
    E -->|是| F[完成 ✅]
    E -->|否| G[提示失败<br/>用户决定下一步]
    C -->|否| H[选择下载工具]
    H --> I[提交离线下载]
    I --> J[aria2/qBit 下载]
    J --> K[下载完成]
    K --> L{目标是天翼云?}
    L -->|是| M[计算 MD5 尝试秒传]
    M --> N{秒传成功?}
    N -->|是| F
    N -->|否| O[普通上传]
    O --> F
    L -->|否| O
Loading

CAS 秒传接口说明

秒传使用新版 initMultiUpload 接口(同时传 fileMd5 + sliceMd5 + sliceSize),与正常上传使用的接口完全一致,确保能匹配到通过新版接口上传的文件。旧版 createUploadFile.action 接口作为回退方案。

设计要点

  • piece size = 10MB:BT 分片与天翼云 CAS 分片完全对齐
  • x-cas 扩展放在 info 外部:不影响 info_hash,普通 BT 客户端忽略该扩展
  • md5sum 使用 BT 标准字段:最大兼容性
  • 通用 torrent 生成pkg/torrent 包不依赖任何驱动,所有存储均可使用
  • ED2K 自动路由:aria2 不支持 ED2K 协议,自动路由到迅雷系工具处理
  • CAS 秒传优先:有 CAS 信息时默认走秒传路径,隐藏下载工具选择器
  • 新版接口秒传:使用 initMultiUpload + commitMultiUploadFile 接口,兼容新旧上传方式

Motivation and Context / 背景

实现 BT 种子生态与网盘秒传的融合,使得:

  1. 天翼云驱动生成的 BT 种子可直接用于天翼云秒传
  2. 其他 BT 种子可下载后秒传,再写回 CAS 信息支持后续秒传
  3. 魔改 BT 客户端可利用天翼云驱动的种子通过天翼云服务器加速下载

How Has This Been Tested? / 测试

  • 天翼云 PC 驱动上传文件并勾选 generate_torrent,验证 .torrent 文件生成并上传到同目录
  • 解析生成的 .torrent 文件,验证包含正确的 x-cas 扩展信息
  • 使用包含 CAS 信息的 .torrent 文件在天翼云进行秒传
  • 前端选择 .torrent 文件右键离线下载,验证文件列表展示和部分选择
  • 前端输入磁力链/ed2k 链接,验证 SimpleHttp 选项被禁用
  • 转存文件到天翼云时,验证自动尝试秒传逻辑
  • 有 CAS 信息时,验证默认走秒传路径且隐藏下载工具选择器
  • 验证秒传使用新版 initMultiUpload 接口能正确匹配已上传的文件

Checklist / 检查清单

  • I have formatted my code with go fmt or prettier.
    我已使用 go fmtprettier 格式化提交的代码。
  • I have added appropriate labels to this PR (or mentioned needed labels in the description if lacking permissions).
    我已为此 PR 添加了适当的标签(如无权限或需要的标签不存在,请在描述中说明,管理员将后续处理)。
  • I have requested review from relevant code authors using the "Request review" feature when applicable.
    我已在适当情况下使用"Request review"功能请求相关代码作者进行审查。
  • I have read the CONTRIBUTING document.
    我已阅读 CONTRIBUTING 文档。
  • I have updated the repository accordingly (If it's needed).
    我已相应更新了相关仓库(若适用)。

@PIKACHUIM PIKACHUIM self-assigned this May 7, 2026
@PIKACHUIM PIKACHUIM marked this pull request as draft May 7, 2026 09:13
@PIKACHUIM PIKACHUIM added enhancement Module: Driver Driver-Related Issue/PR Module: Task Task, scheduling and other goroutine-based features related and removed Module: Task Task, scheduling and other goroutine-based features related labels May 7, 2026
@PIKACHUIM PIKACHUIM marked this pull request as ready for review May 7, 2026 10:22
@PIKACHUIM PIKACHUIM requested review from Copilot and j2rong4cn and removed request for Copilot May 7, 2026 10:22
@PIKACHUIM
Copy link
Copy Markdown
Member Author

有个争议的点:
1、非天翼云上传的文件是否要支持生成BT
2、如果支持生成,是否内嵌CAS信息

目前CAS信息只作用于天翼云一个网盘:
1、非天翼云生成BT内嵌CAS会浪费算力
2、非天翼云生成的CAS,也必须要有人在天翼云上传后才能秒传,而冷门文件大概率没有人传过

@jyxjjj
Copy link
Copy Markdown
Member

jyxjjj commented May 8, 2026

@codex review

@PIKACHUIM PIKACHUIM requested a review from Copilot May 8, 2026 04:38
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19a18e4ab8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/handles/torrent.go
Comment on lines +311 to +312
// 获取存储和文件信息
storage, actualPath, err := op.GetStorageAndActualPath(reqPath)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce read access before hashing files

When an authenticated user calls /api/fs/torrent/generate with a path they cannot normally access, this handler goes directly from JoinPath to op.Get/op.Link and streams the whole object to compute torrent hashes. Unlike the existing fs read handlers, it never checks the nearest meta with CanAccess/CanRead, so users can make the server read protected files and receive their names, sizes, and per-piece hashes (and CAS MD5s when requested). Add the same access check before resolving and linking the storage object.

Useful? React with 👍 / 👎.

Comment on lines +250 to +253
// 上传成功后,异步生成 torrent 文件
// 判断目标是否为天翼云,决定是否注入 CAS 信息
_, is189 := t.DstStorage.(*_189pc.Cloud189PC)
go generateTorrentForFile(t.SrcActualPath, is189)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Generate the torrent before deleting the temp file

For offline-download transfers using delete_on_upload_succeed or delete_always, Run returns immediately after launching this goroutine, and OnSucceeded then calls removeStdTemp on t.SrcActualPath. If the goroutine has not opened the file yet, torrent generation logs “source file does not exist” and the newly added automatic torrent output is lost; the rapid-upload path has the same race. Generate synchronously before returning, or keep the temp file until the goroutine finishes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds torrent/ed2k/magnet offline-download support and integrates Cloud189 (天翼云) CAS rapid-upload data into torrent workflows, including backend APIs for parsing/rapid-upload/generating torrents and driver-side hash/torrent generation during upload/transfer flows.

Changes:

  • Added /api/fs/torrent/* endpoints to parse torrents, rapid-upload from embedded CAS metadata, and generate torrents for existing files.
  • Introduced a new pkg/torrent package (bencode + torrent structs + hashing/generation helpers) to encode/decode torrents and embed x-cas extension data.
  • Enhanced offline-download tooling/189 drivers to handle ed2k routing/aria2 limitations and to compute SHA-1 piece hashes + generate torrents after uploads/transfers.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
server/router.go Registers new torrent-related FS API routes.
server/handles/torrent.go Implements torrent parse/upload-parse/rapid-upload/generate handlers.
pkg/torrent/bencode.go Adds bencode encoder/decoder used by torrent parsing/generation.
pkg/torrent/torrent.go Defines torrent/CAS structures and encode/decode + infohash helpers.
pkg/torrent/hash_writer.go Adds streaming hash accumulator (MD5 + slice MD5 + SHA-1 piece hashes).
pkg/torrent/generate.go Adds reader/file-based torrent generation helpers (with/without CAS).
internal/offline_download/tool/transfer.go Adds Cloud189 rapid-upload attempt during transfer + async torrent generation.
internal/offline_download/tool/add.go Adds SimpleHttp scheme rejection and ed2k tool auto-routing.
internal/offline_download/aria2/aria2.go Adds explicit aria2 ed2k rejection with a clearer error.
drivers/189pc/utils.go Computes SHA-1 piece hashes during stream upload and uploads generated .torrent.
drivers/189pc/torrent.go Adds CAS-in-torrent generation + rapid-upload-from-torrent and CAS helpers.
drivers/189pc/meta.go Adds generate_torrent driver config option.
drivers/189/util.go Computes SHA-1 piece hashes during upload and attempts async torrent generation.
drivers/189/torrent.go Adds CAS-in-torrent generation + rapid-upload-from-torrent and helpers for 189 driver.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/handles/torrent.go
Comment on lines +63 to +74
var req ParseTorrentReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}

// Base64 解码
torrentData, err := base64.StdEncoding.DecodeString(req.TorrentData)
if err != nil {
common.ErrorResp(c, fmt.Errorf("无效的 Base64 编码: %w", err), 400)
return
}
Comment thread server/handles/torrent.go
Comment on lines +83 to +90
resp := ParseTorrentResp{
Name: t.Info.Name,
TotalSize: t.GetTotalSize(),
PieceLength: t.Info.PieceLength,
PieceCount: len(t.Info.Pieces) / 20,
InfoHash: t.GetInfoHashHex(),
HasCAS: t.HasCASInfo(),
}
Comment thread server/handles/torrent.go
if err != nil {
common.ErrorResp(c, fmt.Errorf("获取目标目录失败: %w", err), 500)
return
}
Comment thread server/handles/torrent.go
Comment on lines +196 to +201
// 尝试秒传
obj, err := cloud189PC.RapidUploadFromTorrent(c.Request.Context(), dstDir, torrentData, true)
if err != nil {
common.ErrorResp(c, fmt.Errorf("秒传失败: %w", err), 500)
return
}
Comment thread server/handles/torrent.go
Comment on lines +305 to +316
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}

// 获取存储和文件信息
storage, actualPath, err := op.GetStorageAndActualPath(reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
Comment thread drivers/189pc/torrent.go
Comment on lines +67 to +69
// 计算 sliceMd5(与上传时一致的算法)
sliceMd5Hex := cas.FileMD5
if len(cas.SliceMD5s) > 1 {
Comment thread drivers/189/util.go
Comment on lines +410 to +422
// 生成 torrent 文件
go func() {
fileMD5Upper := strings.ToUpper(fileMd5)
torrentData, err := GenerateTorrent(file.GetName(), file.GetSize(), fileMD5Upper, md5s, DEFAULT, pieceSHA1Hashes)
if err != nil {
log.Warnf("生成 torrent 失败: %v", err)
return
}
infoHash, _ := GetInfoHashHex(torrentData)
log.Infof("已生成 torrent: %s.torrent (info_hash: %s, size: %d bytes)",
file.GetName(), infoHash, len(torrentData))
}()

Comment thread drivers/189pc/torrent.go
Comment on lines +240 to +244

fileMD5Hash := utils.MD5.NewFunc()
sliceMD5s := make([]string, 0)

buf := make([]byte, sliceSize)
Comment thread pkg/torrent/bencode.go
Comment on lines +203 to +214
lenBuf.WriteByte(b)
}
length, err := strconv.ParseInt(lenBuf.String(), 10, 64)
if err != nil {
return nil, fmt.Errorf("bencode: invalid string length: %v", err)
}
data := make([]byte, length)
_, err = io.ReadFull(r, data)
if err != nil {
return nil, err
}
return data, nil
Comment on lines +41 to +52
// NewHashWriter 创建一个新的 HashWriter
// sliceSize: CAS 分片大小(通常 10MB)
// pieceSize: BT piece 大小(设为与 sliceSize 相同以保持对齐)
func NewHashWriter(sliceSize, pieceSize int64) *HashWriter {
return &HashWriter{
fileMD5: md5.New(),
sliceMD5: md5.New(),
pieceSHA1: sha1.New(),
sliceSize: sliceSize,
pieceSize: pieceSize,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Module: Driver Driver-Related Issue/PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants