Skip to content

feat(halalcloud): add HalalCloudOpen offline download - #3006

Open
laorentou666 wants to merge 6 commits into
OpenListTeam:mainfrom
laorentou666:feat/halalcloud-open-offline-download
Open

feat(halalcloud): add HalalCloudOpen offline download#3006
laorentou666 wants to merge 6 commits into
OpenListTeam:mainfrom
laorentou666:feat/halalcloud-open-offline-download

Conversation

@laorentou666

@laorentou666 laorentou666 commented Aug 31, 2026

Copy link
Copy Markdown

Summary / 摘要

为6盘驱动添加离线下载功能

已在6盘缓存的magnet经测试可以离线,未在6盘缓存的种子还未测试

  • This PR has breaking changes.
    / 此 PR 包含破坏性变更。
  • This PR changes public API, config, storage format, or migration behavior.
    / 此 PR 修改了公开 API、配置、存储格式或迁移行为。
  • This PR requires corresponding changes in related repositories.
    / 此 PR 需要关联仓库同步修改。

Related repository PRs / 关联仓库 PR:

  • OpenList-Frontend:
  • OpenList-Docs:

Related Issues / 关联 Issue

Relates to #2215

Testing / 测试

  • go test ./...
  • Manual test / 手动测试:
  • go test ./drivers/halalcloud_open ./internal/offline_download/halalcloud_open ./internal/offline_download/tool
  • go test -race ./drivers/halalcloud_open ./internal/offline_download/halalcloud_open ./internal/offline_download/tool
  • go test -vet=off ./internal/offline_download/...

Checklist / 检查清单

  • I have read CONTRIBUTING.
    / 我已阅读 CONTRIBUTING
  • I confirm this contribution follows the repository license, contribution policy, and code of conduct.
    / 我确认此贡献符合仓库许可证、贡献规范和行为准则。
  • I have formatted the changed code with gofmt, go fmt, or prettier where applicable.
    / 我已按适用情况使用 gofmtgo fmtprettier 格式化变更代码。
  • I have requested review from relevant maintainers or code owners where applicable.
    / 我已在适用情况下请求相关维护者或代码所有者审查。

AI Disclosure / AI 使用声明

  • This PR includes AI-assisted content.
    / 此 PR 包含 AI 辅助内容。

Tools used / 使用工具:

  • ChatGPT
  • Codex
  • GitHub Copilot
  • Claude
  • Gemini
  • Other (please specify) / 其他(请注明):

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-By attribution.
    / 我已确保所有 AI 辅助提交都包含 Co-Authored-By 归属信息。

  • I can reproduce all AI-assisted content included in this PR without any AI tools.
    / 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。

laorentou666 and others added 4 commits August 31, 2026 12:47
- 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>
@laorentou666
laorentou666 force-pushed the feat/halalcloud-open-offline-download branch from ec36251 to cb7ec8f Compare August 31, 2026 16:00
- 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>
@laorentou666
laorentou666 force-pushed the feat/halalcloud-open-offline-download branch from cb7ec8f to 1aa097c Compare September 1, 2026 05:32

@pikachuren pikachuren left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🙏 感谢 @laorentou666 提交!

🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。

⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。

⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。


🎯 结论

建议合并(Approve),仅需补一次 CI 验证

这是一个质量较高的 PR:除了新增 HalalCloudOpen 离线下载驱动,还对公共缓存层做了一处有价值的并发安全改进(目录缓存版本号机制),且所有公共层改动都带测试。分页防死循环、路径边界处理等细节也做得很到位。唯一顾虑是分支上没有 CI 记录,建议合并前触发一次构建。

📖 概要

新增 HalalCloudOpen 的离线下载支持(驱动 + 离线下载工具),并顺带改造了 internal/cache 的目录缓存删除逻辑,引入版本号机制防止并发下的缓存竞态。

🧭 整体方案

两个层面:

  1. 功能层drivers/halalcloud_open(驱动,含 rate_limit)+ internal/offline_download/halalcloud_open(离线下载实现)。
  2. 公共层改造internal/cache 新增 DeletePrefix / DeleteKeyPrefix(按路径前缀删除),并把 DeleteDirectoryTree 从递归 Pop 改为「版本号递增 + 前缀删除」,解决原有递归删除在并发下可能漏删或误删的问题。

公共层改造的动机是合理的:离线下载完成后需要精确失效缓存,而原来的 deleteDirectoryTree 用递归 Pop,既慢又在并发时有竞态。新的版本号机制(dirVersionMu + dirVersions)让写入方在版本变化时放弃过期写入,这是正确的并发控制手段。

📊 变更统计

  • 文件:21 个,+1326 / -35
  • 公共层:internal/cache/*internal/op/cache.gointernal/op/{fs,path}.gointernal/offline_download/tool/*
维度 评分 说明
功能 ⭐⭐⭐⭐⭐ 需求完整,公共层改进有额外价值
最小改动 ⭐⭐⭐⭐ 驱动纯新增,公共层改动聚焦
前向兼容 ⭐⭐⭐⭐⭐ 无破坏性
方案设计 ⭐⭐⭐⭐⭐ 缓存版本号机制设计得当

🚨 关键问题

P0 阻塞问题

无。

P1 建议修复

1)合并前请触发一次 CI

分支 feat/halalcloud-open-offline-download 上没有任何 CI 记录(gh pr checks 返回 no checks reported)。考虑到本 PR 改动了 internal/opinternal/cache 这两个公共层,编译验证尤其重要。请问是否可以触发一次全平台构建呢?

P2 可选优化

2)directoryVersion 按存储(mount path)粒度递增,可能过度失效

func directoryVersionKey(storage driver.Driver) string {
	return utils.GetActualMountPath(storage.GetStorage().MountPath)
}

版本号按「整个存储」为粒度递增。这意味着对存储内任意一个目录的删除,都会使该存储下所有正在进行的目录缓存写入失效。在高并发、多目录的场景下,这会导致缓存命中率下降(虽然正确性没问题)。

想请教下是否考虑过更细的粒度?比如按「被删除目录的路径」为版本键,只失效受影响的子树。当然这会增加复杂度,当前的全局版本号是「正确优先」的稳妥选择,如果性能可接受,维持现状也完全合理。

3)updateDirectoryCachedirVersionMu 用了 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,   // 分页 token

ClientID / ClientSecret 是 HalalCloudOpen 官方 API 的凭据配置(从 Addition 读取,非硬编码),token 是分页游标。未发现命令执行、非预期外连、TLS 校验降级、硬编码密钥

📂 逐文件分析

internal/cache/keyed_cache.gotyped_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)为 dirVersionMudirCache 的锁顺序补充注释约定
  • (P2)可选:把分页防死循环逻辑抽成通用助手
  • 恶意模式扫描(干净)
  • 依赖核查(无新增)
  • 公共层改动的测试覆盖(prefix_test、cache_test、offline_test 等)

🎯 结论:建议合并(补 CI 后)。功能实现完整,公共层的缓存版本号机制是一次扎实的并发安全改进,pathPrefixMatch 的边界处理与配套测试尤其值得肯定。唯一需要的是补一次构建验证。感谢您的贡献!

Signed-off-by: laorentou666 <89732460+laorentou666@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants