Skip to content

feat(drivers): add GuangYaPan driver with offline download support - #2882

Open
PIKACHUIM wants to merge 1 commit into
mainfrom
dev-guuangya
Open

feat(drivers): add GuangYaPan driver with offline download support#2882
PIKACHUIM wants to merge 1 commit into
mainfrom
dev-guuangya

Conversation

@PIKACHUIM

@PIKACHUIM PIKACHUIM commented Jul 31, 2026

Copy link
Copy Markdown
Member

feat(drivers): add GuangYaPan driver with offline download

Summary / 摘要

This PR adds the GuangYaPan cloud drive driver to OpenList, refactoring
PR #2583. It implements the
full driver (List / Link / MakeDir / Rename / Remove / Move / Copy / Put / GetDetails),
offline download (magnet / torrent / HTTP / FTP), three login methods (access token /
refresh token / SMS), OSS multipart upload, per-endpoint rate limiting, and concurrent
token refresh. All community review comments were addressed, and several issues found
during review were fixed — including a runtime upload failure
(Get upload token failed: 上传已完成) when the backend reports the file is already
uploaded / instant-uploaded.

  • New driver drivers/guangyapan with all 12 core interfaces implemented.
  • Offline download support under internal/offline_download/guangyapan.
  • ClientID is now a required user-supplied field (reverse-engineered default
    client constant removed per review).
  • Fixed upload failure when the backend returns Msg="上传已完成" (already uploaded /
    instant upload): the token request is now treated as a successful fast-upload and
    skips the OSS upload step, waiting for the async task instead.

Review comments addressed (PR #2583)

Reviewer Comment Status
@PIKACHUIM Use OpenListTeam go-cache Done
@PIKACHUIM No Chinese in help fields Done (all help in English)
@PIKACHUIM Move utility funcs out of driver.go Done (util.go, 13 helpers)
@xrgzs Remove reverse-engineered defaultClient Done (ClientID now required)
@xrgzs Implement GetDetails Done (via /nd.bizassets.s/v1/get_assets)
@xrgzs Implement all driver features in drivers/guangyapan Done (12 interfaces)
@xrgzs Keep non-driver changes minimal Done (framework-level registrations only)
  • 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

Changes / 变更文件清单

New (7 files, 1794 lines)

File Lines Description
drivers/guangyapan/driver.go 784 Core driver (12 interface methods + auth / root resolution)
drivers/guangyapan/util.go 359 HTTP / upload / normalization helpers
drivers/guangyapan/types.go 231 All API response type definitions
drivers/guangyapan/offline.go 169 Offline download API (Resolve / Create / List / Delete)
drivers/guangyapan/meta.go 42 driver.Config and Addition definitions
internal/offline_download/guangyapan/guangyapan.go 132 Offline download tool registration
internal/offline_download/guangyapan/util.go 77 Offline task cache (go-cache + singleflight)

Modified (7 files, +66 / -1)

File Change Category
drivers/all.go +1 Driver global registration
internal/conf/const.go +3 New GuangYaPanTempDir
internal/offline_download/all.go +1 Offline tool global registration
internal/offline_download/tool/add.go +11 New case "GuangYaPan" branch
internal/offline_download/tool/download.go +5 New driver type match
server/handles/offline_download.go +45 New SetGuangYaPan handler
server/router.go +1 New /set_guangyapan route

All non-drivers/guangyapan changes are framework-level registrations required by
the architecture (same pattern as pikpak, thunder, 115_open); they cannot be
moved into the driver package without introducing import cycles.

Fixes / 修复清单

Critical (compile / runtime failures)

# Issue Fix
C1 Put signature mismatch (compile error) Changed to (model.Obj, error) to match driver.PutResult
C2 Redundant access_token empty-check in postAPI Fallback to d.ensureAccessToken(ctx)
C3 refreshToken concurrent data race sync.Mutex + double-check in GuangYaPan
C4 resty.Client.SetHeader concurrent write Use per-Request.SetHeader instead of mutating client header

Major

# Issue Fix
M1 refreshTaskCache bool not concurrency-safe Changed to atomic.Bool
M2 Move/Copy reused deleteResp type Added dedicated taskResp
M3 Unused deleteFiles param in DeleteOfflineTasks Removed
M4 ensureAccessToken contained SMS login path Simplified to refresh only; SMS login moved to Init
M5 List / findChildFolderID pagination unbounded Added maxPage = 10000 guard
M6 Sensitive tokens shown as type:"text" Removed type:"text" (hidden by default)
M7 setTempStatus timer not cancelled on Drop Track statusTimer, Stop() in Drop
M8 Hard-coded reverse-engineered X-Device-Sign suffix Added configurable device_sign field

Minor

# Issue Fix
m1 Redundant empty fileTypes: []int{} Removed
m2 IsSuccess() vs isSuccessMsg inconsistency Unified on isSuccessMsg
m3 deleteResp / taskResp fully duplicated Merged into single taskResp
m4 Scattered EqualFold("success") checks All routed through isSuccessMsg()
m5 model.Object.Ctime field confirmation Verified and used correctly
m6 Object.Path stores parentID (ambiguous) Kept (internally consistent at driver layer)

Upload bug fixed this round (PR follow-up)

  • Symptom: Get upload token failed: 上传已完成 on creating/updating a file.
  • Root cause: The backend returns Msg="上传已完成" (file already uploaded /
    instant upload) with a valid TaskID but no OSS credentials. The old
    getUploadToken rejected this non-success Msg as an error; even if allowed, Put
    only skipped OSS upload on code == 156, so it still failed.
  • Fix:
    1. uploadTokenData gains an AlreadyDone bool field (json:"-").
    2. getUploadToken treats Msg values like 上传已完成 / 秒传成功 /
      upload completed / already uploaded as success and sets AlreadyDone.
    3. Put skips the OSS upload and calls waitUploadTaskInfo when
      code == 156 || token.AlreadyDone.
    4. Functional match strings (上传已完成, 秒传成功) are intentionally kept
      because they are the exact Msg values returned by the real backend API.

Architecture / 架构设计

Auth flow

flowchart TD
    Start[Init] --> HasAccess{has AccessToken?}
    HasAccess -->|Yes| Validate[Validate Token]
    Validate -->|OK| PrepareRoot[Prepare Root Folder]
    Validate -->|Fail| ClearAT[Clear AccessToken]
    ClearAT --> HasRefresh
    HasAccess -->|No| HasRefresh{has RefreshToken?}
    HasRefresh -->|Yes| Refresh[Refresh Token]
    Refresh -->|OK| PrepareRoot
    HasRefresh -->|No| HasPhone{has PhoneNumber?}
    HasPhone -->|Yes,VerifyCode| SMSLogin[SMS Login]
    SMSLogin --> PrepareRoot
    HasPhone -->|Yes,SendCode| SendSMS[Send SMS Code]
    HasPhone -->|No| Fail[Fail: no auth method]
Loading

Concurrency protection

flowchart LR
    A[Concurrent postAPI] --> B{AccessToken valid?}
    B -->|401/403| C[refreshToken]
    C --> D[refreshMu.Lock]
    D --> E{Already refreshed?}
    E -->|Yes| F[Return OK]
    E -->|No| G[Do refresh + SaveStorage]
    G --> F
    F --> H[Retry postAPI]
Loading

Configuration / 配置项说明

Field Type Required Description
client_id string GuangYaPan API client ID
phone_number string alt SMS login phone (e.g. +86 13800000000)
captcha_token string optional Captcha token (needed for first SMS login)
send_code bool optional Set true and save to send verification code
verify_code string SMS Received SMS verification code
access_token string alt Existing Bearer token
refresh_token string alt Existing refresh token (recommended)
device_id string optional 32-hex device ID, auto-generated if empty
device_sign string optional Custom X-Device-Sign header
root_path string optional Root folder path inside the drive
page_size int optional Page size, default 100
order_by int optional Sort field (0-4)
sort_type int optional Sort direction (0=asc, 1=desc)

Login flows

Method A — Access Token (recommended)

1. Fill access_token + client_id
2. Save -> Init validates automatically -> done

Method B — Refresh Token (recommended, long-lived)

1. Fill refresh_token + client_id
2. Save -> auto-refreshed to access_token -> done

Method C — SMS two-step

Step 1: Fill phone_number + captcha_token + send_code=true -> Save
Step 2: After receiving SMS, fill verify_code -> Save -> login complete

Compatibility / 兼容性

  • Go version: aligned with the project (1.25+).
  • Dependencies: reuses existing github.com/OpenListTeam/go-cache,
    github.com/aliyun/aliyun-oss-go-sdk, github.com/go-resty/resty/v2,
    golang.org/x/time/rate. No new third-party dependencies.
  • No breaking changes to existing drivers or handlers; backward compatible with
    existing storage configuration.

Testing / 测试

  • go test ./...
  • Manual test / 手动测试:
    • Lint clean for drivers/guangyapan and internal/offline_download/guangyapan (0 errors).
    • Logic verified: backend returning Msg="上传已完成" with a valid TaskID
      now returns success instead of Get upload token failed.
    • Suggested manual checks:
      • Full token auth paths (access / refresh / SMS)
      • Large-file upload (>16GB triggers 4MB parts)
      • Cross-drive copy (triggers API rate limiting)
      • Concurrency (10+ parallel requests triggering refresh, verify no data race)
      • Magnet / torrent / HTTP URL offline download
      • Root path multi-level folder resolution
    • go build ./drivers/guangyapan/... was not run in this environment
      (no Go toolchain available); please run it before merge.

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 辅助内容。

Acknowledgements / 致谢

@PIKACHUIM
PIKACHUIM requested review from jyxjjj and xrgzs July 31, 2026 09:39
@xrgzs xrgzs added enhancement Module: Driver Driver-Related Issue/PR labels Jul 31, 2026
Comment on lines +224 to +250
func (d *GuangYaPan) multipartUploadToOSS(ctx context.Context, bucket *oss.Bucket, objectPath string, file model.FileStreamer, up driver.UpdateProgress) error {
partSize := calcUploadPartSize(file.GetSize())
imur, err := bucket.InitiateMultipartUpload(objectPath, oss.Sequential())
if err != nil {
return err
}

total := file.GetSize()
partCount := int((total + partSize - 1) / partSize)
parts := make([]oss.UploadPart, 0, partCount)
var uploaded int64
partNumber := 1

for uploaded < total {
if err := ctx.Err(); err != nil {
return err
}
curPartSize := partSize
left := total - uploaded
if left < curPartSize {
curPartSize = left
}

reader := io.LimitReader(file, curPartSize)
part, err := bucket.UploadPart(imur, driver.NewLimitedUploadStream(ctx, reader), curPartSize, partNumber)
if err != nil {
return err

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

上传流处理有问题,需要添加一下分片重试。

  1. 没有 hybrid cache 集成:直接通过 io.LimitReader 从原始 FileStreamer 顺序读取。FileStreamer 本质上是单向流(只实现了 io.Reader),一旦某一分片上传失败需要重试时,io.LimitReader 已经消耗了对应字节,无法回退重读

  2. 没有 stream.NewStreamSectionReader:参考驱动(quark_open、aliyundrive_open、115_open、pikpak)都使用 stream.NewStreamSectionReader(file, int(partSize), &up) 来获得可 Seek 的 SectionReader,确保分片上传失败后可以 rd.Seek(0, io.SeekStart) 重读。

  3. 没有 retry 包装:参考驱动在分片上传外层都有 retry.Do + rd.Seek(0, io.SeekStart) 重试逻辑,guangyapan 没有。

建议修复 multipartUploadToOSS,参照 quark_open/aliyundrive_open 的模式,改为使用 stream.NewStreamSectionReader 获取可重读的分片 reader,并用 retry.Do 包装分片上传逻辑。

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.

2 participants