feat(halalcloud): add HalalCloudOpen offline download - #3006
feat(halalcloud): add HalalCloudOpen offline download#3006laorentou666 wants to merge 6 commits into
Conversation
- Add HalalCloudOpen offline task API wrappers for create, list, and delete operations. - Register a destination-bound tool with shared polling cache and documented provider status handling. - Cover URL forwarding, pagination, status boundaries, cache invalidation, and native storage routing. Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
- Enforce the documented API request limit across clients sharing one APP key. - Invalidate task and destination caches after terminal or canceled downloads. - Document provider identity, path, polling, and transfer semantics. - Normalize progress text and cover rate limiting and progress bounds with tests. Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
- Condense comments around task identity, provider paths, cache invalidation, and direct writes. - Describe offline task states using the documented numeric categories. - Simplify the credential-scoped one-QPS limiter while preserving request cancellation. Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
- Recursively invalidate destination directory trees after terminal, missing, or canceled tasks. - Add regression coverage proving descendant listings refresh after provider-side writes. Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
ec36251 to
cb7ec8f
Compare
- Persist the selected storage mount so balanced backends remain stable across add, status, and cleanup. - Bound provider polling and cancellation cleanup while preserving shared task-list requests. - Invalidate cached directory and link descendants by path prefix, with regression coverage. Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
cb7ec8f to
1aa097c
Compare
pikachuren
left a comment
There was a problem hiding this comment.
🙏 感谢 @laorentou666 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。
🎯 结论
建议合并(Approve),仅需补一次 CI 验证。
这是一个质量较高的 PR:除了新增 HalalCloudOpen 离线下载驱动,还对公共缓存层做了一处有价值的并发安全改进(目录缓存版本号机制),且所有公共层改动都带测试。分页防死循环、路径边界处理等细节也做得很到位。唯一顾虑是分支上没有 CI 记录,建议合并前触发一次构建。
📖 概要
新增 HalalCloudOpen 的离线下载支持(驱动 + 离线下载工具),并顺带改造了 internal/cache 的目录缓存删除逻辑,引入版本号机制防止并发下的缓存竞态。
🧭 整体方案
两个层面:
- 功能层:
drivers/halalcloud_open(驱动,含 rate_limit)+internal/offline_download/halalcloud_open(离线下载实现)。 - 公共层改造:
internal/cache新增DeletePrefix/DeleteKeyPrefix(按路径前缀删除),并把DeleteDirectoryTree从递归Pop改为「版本号递增 + 前缀删除」,解决原有递归删除在并发下可能漏删或误删的问题。
公共层改造的动机是合理的:离线下载完成后需要精确失效缓存,而原来的 deleteDirectoryTree 用递归 Pop,既慢又在并发时有竞态。新的版本号机制(dirVersionMu + dirVersions)让写入方在版本变化时放弃过期写入,这是正确的并发控制手段。
📊 变更统计
- 文件:21 个,+1326 / -35
- 公共层:
internal/cache/*、internal/op/cache.go、internal/op/{fs,path}.go、internal/offline_download/tool/*
| 维度 | 评分 | 说明 |
|---|---|---|
| 功能 | ⭐⭐⭐⭐⭐ | 需求完整,公共层改进有额外价值 |
| 最小改动 | ⭐⭐⭐⭐ | 驱动纯新增,公共层改动聚焦 |
| 前向兼容 | ⭐⭐⭐⭐⭐ | 无破坏性 |
| 方案设计 | ⭐⭐⭐⭐⭐ | 缓存版本号机制设计得当 |
🚨 关键问题
P0 阻塞问题
无。
P1 建议修复
1)合并前请触发一次 CI
分支 feat/halalcloud-open-offline-download 上没有任何 CI 记录(gh pr checks 返回 no checks reported)。考虑到本 PR 改动了 internal/op 和 internal/cache 这两个公共层,编译验证尤其重要。请问是否可以触发一次全平台构建呢?
P2 可选优化
2)directoryVersion 按存储(mount path)粒度递增,可能过度失效
func directoryVersionKey(storage driver.Driver) string {
return utils.GetActualMountPath(storage.GetStorage().MountPath)
}版本号按「整个存储」为粒度递增。这意味着对存储内任意一个目录的删除,都会使该存储下所有正在进行的目录缓存写入失效。在高并发、多目录的场景下,这会导致缓存命中率下降(虽然正确性没问题)。
想请教下是否考虑过更细的粒度?比如按「被删除目录的路径」为版本键,只失效受影响的子树。当然这会增加复杂度,当前的全局版本号是「正确优先」的稳妥选择,如果性能可接受,维持现状也完全合理。
3)updateDirectoryCache 中 dirVersionMu 用了 RLock,但内部又调用了 deleteDirectoryTree
func (cm *CacheManager) updateDirectoryCache(storage driver.Driver, key string, version uint64, value *directoryCache, ttl time.Duration) bool {
cm.dirVersionMu.RLock()
defer cm.dirVersionMu.RUnlock()
if cm.dirVersions[directoryVersionKey(storage)] != version {
return false
}
if value == nil {
cm.deleteDirectoryTree(key)
} else {
cm.dirCache.SetWithTTL(key, value, ttl)
}
return true
}deleteDirectoryTree 内部会操作 cm.dirCache(有它自己的锁),这里不构成死锁,但 dirVersionMu 的读锁被持有时又去碰其他锁,锁的嵌套层级需要留意后续维护。建议确认没有反向的获取顺序(其他路径先拿 dirCache 锁再拿 dirVersionMu)——如果存在反向顺序,会有死锁风险。从当前 diff 看,dirVersionMu 只在 DeleteDirectoryTree/DeleteDirectory/updateDirectoryCache 三个地方短促持有,暂时没有反向嵌套,但值得在注释里明确锁顺序约定。
4)OfflineList 的分页防御逻辑可以抽成通用助手
seenTokens := make(map[string]struct{})
// ...
if nextToken == token { return nil, errors.New("...pagination token did not advance") }
if _, ok := seenTokens[nextToken]; ok { return nil, errors.New("...pagination token repeated") }这个「防 token 不前进 / 防 token 重复」的双重防御写得很好,是离线下载轮询里容易出死循环的地方。考虑到项目里其他离线下载工具(pikpak、thunder 等)可能也有类似的分页逻辑,想请教下是否值得把它抽成一个通用的分页遍历助手供复用?(这条是可选建议,不抽也完全 OK。)
🔐 依赖安全审查
结论:未发现供应链风险。
本 PR 未引入任何新的第三方依赖,go.mod / go.sum 无改动。
对新增代码做了恶意模式扫描,命中的都是正常的字段名/配置项:
client := apiclient.NewClient(httpClient, host, d.Addition.ClientID, d.Addition.ClientSecret, ...)
Token: token, // 分页 tokenClientID / ClientSecret 是 HalalCloudOpen 官方 API 的凭据配置(从 Addition 读取,非硬编码),token 是分页游标。未发现命令执行、非预期外连、TLS 校验降级、硬编码密钥。
📂 逐文件分析
internal/cache/keyed_cache.go、typed_cache.go(+29 / +12)
改动意图:新增按路径前缀删除能力。
代码逻辑:
func pathPrefixMatch(key, prefix string) bool {
if key == prefix {
return true
}
if prefix == "/" {
return strings.HasPrefix(key, "/")
}
return strings.HasPrefix(key, prefix+"/")
}问题分析:这个 pathPrefixMatch 是正确的——用 prefix+"/" 做前缀匹配,避免了 /foo 误匹配 /foobar 的经典 bug,且 prefix == "/" 的边界也单独处理了。配套的 prefix_test.go 恰好覆盖了 /foo 与 /foobar 的兄弟键场景,测试质量高。
详细建议:无需改动。
internal/op/cache.go(版本号机制)
改动意图:DeleteDirectoryTree 改为版本号递增 + 前缀删除,替代递归 Pop。
代码逻辑:删除时 dirVersions[key]++,写入方 updateDirectoryCache 校验版本一致才写入,否则放弃。
问题分析:这是本 PR 最有价值的部分。原先的 deleteDirectoryTree 递归 Pop 在并发下有竞态(删除进行中,新的缓存写入可能又被删掉或漏删)。版本号机制用「乐观锁」思路解决:任何删除操作使版本失效,旧的写入方(带着旧版本号)会放弃。设计正确。
需要留意的是锁的粒度(P2-2、P2-3)。
详细建议:见 P2-2、P2-3。
internal/offline_download/halalcloud_open/*、drivers/halalcloud_open/*
改动意图:HalalCloudOpen 的驱动与离线下载实现。
代码逻辑:driver(含 rate_limit.go 限流)、offline 的 add/list/remove 流程。
问题分析:实现完整,rate_limit.go 自带了限流逻辑与测试,offline_test.go 也覆盖了主要流程。分页防御(P2-4)做得细心。
详细建议:见 P2-4。
internal/offline_download/tool/*、internal/op/{fs,path}.go
改动意图:接入新工具、暴露目录缓存失效。
代码逻辑:add.go 增加 halalcloud_open 分支,op 层用版本号机制替代旧的缓存删除调用。
问题分析:改动聚焦,无阻塞问题。
详细建议:无。
✅ 待处理清单
- (P1)触发一次全平台 CI 验证
- (P2)评估目录版本号的粒度是否需按路径细化
- (P2)为
dirVersionMu与dirCache的锁顺序补充注释约定 - (P2)可选:把分页防死循环逻辑抽成通用助手
- 恶意模式扫描(干净)
- 依赖核查(无新增)
- 公共层改动的测试覆盖(prefix_test、cache_test、offline_test 等)
🎯 结论:建议合并(补 CI 后)。功能实现完整,公共层的缓存版本号机制是一次扎实的并发安全改进,pathPrefixMatch 的边界处理与配套测试尤其值得肯定。唯一需要的是补一次构建验证。感谢您的贡献!
Signed-off-by: laorentou666 <89732460+laorentou666@users.noreply.github.com>
Summary / 摘要
为6盘驱动添加离线下载功能
已在6盘缓存的magnet经测试可以离线,未在6盘缓存的种子还未测试
/ 此 PR 包含破坏性变更。
/ 此 PR 修改了公开 API、配置、存储格式或迁移行为。
/ 此 PR 需要关联仓库同步修改。
Related repository PRs / 关联仓库 PR:
Related Issues / 关联 Issue
Relates to #2215
Testing / 测试
go test ./...Checklist / 检查清单
/ 我已阅读 CONTRIBUTING。
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
gofmt,go fmt, orprettierwhere applicable./ 我已按适用情况使用
gofmt、go fmt或prettier格式化变更代码。/ 我已在适用情况下请求相关维护者或代码所有者审查。
AI Disclosure / AI 使用声明
/ 此 PR 包含 AI 辅助内容。
Tools used / 使用工具:
Usage scope / 使用范围:
Code generation / 代码生成
Refactoring / 重构
Documentation / 文档
Tests / 测试
Translation / 翻译
Review assistance / 审查辅助
I have reviewed and validated all AI-assisted content included in this PR.
/ 我已审核并验证此 PR 中的所有 AI 辅助内容。
I have ensured that all AI-assisted commits include
Co-Authored-Byattribution./ 我已确保所有 AI 辅助提交都包含
Co-Authored-By归属信息。I can reproduce all AI-assisted content included in this PR without any AI tools.
/ 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。