Conversation
|
有个争议的点: 目前CAS信息只作用于天翼云一个网盘: |
|
@codex review |
There was a problem hiding this comment.
💡 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".
| // 获取存储和文件信息 | ||
| storage, actualPath, err := op.GetStorageAndActualPath(reqPath) |
There was a problem hiding this comment.
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 👍 / 👎.
| // 上传成功后,异步生成 torrent 文件 | ||
| // 判断目标是否为天翼云,决定是否注入 CAS 信息 | ||
| _, is189 := t.DstStorage.(*_189pc.Cloud189PC) | ||
| go generateTorrentForFile(t.SrcActualPath, is189) |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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/torrentpackage (bencode + torrent structs + hashing/generation helpers) to encode/decode torrents and embedx-casextension 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.
| 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 | ||
| } |
| 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(), | ||
| } |
| if err != nil { | ||
| common.ErrorResp(c, fmt.Errorf("获取目标目录失败: %w", err), 500) | ||
| return | ||
| } |
| // 尝试秒传 | ||
| obj, err := cloud189PC.RapidUploadFromTorrent(c.Request.Context(), dstDir, torrentData, true) | ||
| if err != nil { | ||
| common.ErrorResp(c, fmt.Errorf("秒传失败: %w", err), 500) | ||
| return | ||
| } |
| 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 | ||
| } |
| // 计算 sliceMd5(与上传时一致的算法) | ||
| sliceMd5Hex := cas.FileMD5 | ||
| if len(cas.SliceMD5s) > 1 { |
| // 生成 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)) | ||
| }() | ||
|
|
|
|
||
| fileMD5Hash := utils.MD5.NewFunc() | ||
| sliceMD5s := make([]string, 0) | ||
|
|
||
| buf := make([]byte, sliceSize) |
| 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 |
| // 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, | ||
| } | ||
| } |
Description / 描述
支持BT种子文件/ED2K/磁力链离线下载,支持天翼云CAS秒传
功能支持项
ed2k://和magnet:?链接发起离线下载任务预期的逻辑
后端改动
新增文件
pkg/torrent/bencode.gopkg/torrent/torrent.gopkg/torrent/hash_writer.gopkg/torrent/generate.godrivers/189pc/torrent.godrivers/189/torrent.goserver/handles/torrent.go修改文件
drivers/189pc/meta.goGenerateTorrent配置选项drivers/189pc/utils.godrivers/189/util.gointernal/offline_download/tool/transfer.goserver/router.go新增 API
/api/fs/torrent/parse/api/fs/torrent/upload_parse/api/fs/torrent/rapid_upload/api/fs/torrent/generate前端改动
新增文件
src/types/torrent.tssrc/pages/home/toolbar/OfflineDownloadEnhanced.tsxsrc/pages/home/toolbar/TorrentFileList.tsx修改文件
src/types/index.tssrc/utils/api.tssrc/utils/bus.tstorrent_parsed事件类型src/pages/home/toolbar/operations.tsoffline_download_torrent操作图标src/pages/home/toolbar/Toolbar.tsxsrc/pages/home/folder/context-menu.tsxsrc/lang/en/home.jsonBT文件逻辑
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]离线下载完整流程
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 -->|否| OCAS 秒传接口说明
秒传使用新版
initMultiUpload接口(同时传fileMd5+sliceMd5+sliceSize),与正常上传使用的接口完全一致,确保能匹配到通过新版接口上传的文件。旧版createUploadFile.action接口作为回退方案。设计要点
pkg/torrent包不依赖任何驱动,所有存储均可使用initMultiUpload+commitMultiUploadFile接口,兼容新旧上传方式Motivation and Context / 背景
实现 BT 种子生态与网盘秒传的融合,使得:
How Has This Been Tested? / 测试
Checklist / 检查清单
go fmtor prettier.我已使用
go fmt或 prettier 格式化提交的代码。我已为此 PR 添加了适当的标签(如无权限或需要的标签不存在,请在描述中说明,管理员将后续处理)。
我已在适当情况下使用"Request review"功能请求相关代码作者进行审查。
我已阅读 CONTRIBUTING 文档。
我已相应更新了相关仓库(若适用)。