Skip to content

feat(drivers): add Rakuten Drive support#2084

Open
rufusu wants to merge 7 commits intoOpenListTeam:mainfrom
rufusu:feat/rakutenDrive
Open

feat(drivers): add Rakuten Drive support#2084
rufusu wants to merge 7 commits intoOpenListTeam:mainfrom
rufusu:feat/rakutenDrive

Conversation

@rufusu
Copy link
Copy Markdown

@rufusu rufusu commented Feb 6, 2026

feat(rakuten_drive): add Rakuten Drive storage driver

Description / 描述

  • Added a new RakutenDrive driver with list, link, mkdir, move, rename, copy, delete, upload (S3 multipart), and storage quota support.
  • Implemented token refresh + JWT exp parsing, request wrapper, and path normalization utilities.
  • Registered the driver in drivers/all.go.

Motivation and Context / 背景

  • Enables OpenList to connect to 楽天ドライブ via its API for file operations and uploads.

How Has This Been Tested? / 测试

  • Built and ran the Docker container locally to verify the Rakuten Drive works.

Checklist / 检查清单

  • I have read the CONTRIBUTING document.
    我已阅读 CONTRIBUTING 文档。
  • 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 updated the repository accordingly (If it’s needed).
    我已相应更新了相关仓库(若适用)。
  • OpenList-Frontend #XXXX
  • OpenList-Docs #XXXX

@rufusu rufusu marked this pull request as draft February 6, 2026 04:52
@rufusu rufusu marked this pull request as ready for review February 6, 2026 04:53
@rufusu rufusu marked this pull request as draft February 6, 2026 04:53
@rufusu rufusu marked this pull request as ready for review February 6, 2026 05:08
Comment thread drivers/rakuten_drive/driver.go Outdated
if srcRemote == "" {
srcRemote = d.toRemotePath(srcObj.GetPath(), srcObj.IsDir())
}
prefix := path.Dir(srcRemote)
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.

Maybe you can extract it as a function? It was used four times.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. Will update this to reduce duplication as well.

Comment thread drivers/rakuten_drive/types.go
Comment thread drivers/rakuten_drive/driver.go
Comment thread drivers/rakuten_drive/driver.go Outdated
}
if strings.HasSuffix(itemPath, "/") {
isFolder = true
}
Copy link
Copy Markdown
Member

@dezhishen dezhishen Feb 6, 2026

Choose a reason for hiding this comment

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

Would you mind moving this part into its own method? It would make the code easier to read and maintain.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the suggestion — I’ll update this to reduce duplication.

This comment was marked as duplicate.

@rufusu
Copy link
Copy Markdown
Author

rufusu commented Feb 7, 2026

refactor(rakuten_drive): extract repeated logic into helper methods

Description / 描述

  • Extracted normalizePrefix method to handle directory prefix normalization logic that was repeated in Move, Rename, Copy, and Remove methods.
  • Extracted detectIsFolder method to encapsulate folder detection logic from the parseList method.
  • Improved code maintainability and readability by reducing code duplication.

Motivation and Context / 背景

  • Addressed code review feedback requesting extraction of repeated logic into reusable methods.
  • The prefix normalization pattern was duplicated 4 times across different methods.
  • The folder detection logic in parseList was complex and warranted its own method for better clarity.

How Has This Been Tested? / 测试

  • Verified that the code compiles without errors.
  • Logic behavior remains unchanged, only code organization improved.

Checklist / 检查清单

  • I have read the CONTRIBUTING document.
    我已阅读 CONTRIBUTING 文档。
  • 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 updated the repository accordingly (If it's needed).
    我已相应更新了相关仓库(若适用)。
  • OpenList-Frontend #XXXX
  • OpenList-Docs #XXXX

Comment thread drivers/rakuten_drive/driver.go Outdated
}
if strings.HasSuffix(itemPath, "/") {
isFolder = true
}

This comment was marked as duplicate.

Comment thread drivers/rakuten_drive/util.go Outdated
return time.Unix(exp, 0), nil
}

func (d *RakutenDrive) toRemotePath(p string, isDir bool) string {
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.

Typically, we express conversions as "from A to B" (e.g., a2b), or retrievals as "get something" (e.g., getSomething). This method name fails to accurately convey its purpose.
toLocalPath has the same promble.

@xrgzs xrgzs added enhancement Module: Driver Driver-Related Issue/PR labels Feb 19, 2026
@Suyunmeng Suyunmeng force-pushed the main branch 4 times, most recently from a31fd53 to 7bea29c Compare April 2, 2026 17:27
rufusu and others added 2 commits April 17, 2026 00:52
…2RemotePath/remotePath2LocalPath

Follow project naming conventions as per reviewer feedback
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

Adds a new Rakuten Drive storage driver to the OpenList drivers set, enabling basic file operations and multipart uploads against Rakuten Drive’s API, and registers it for runtime availability.

Changes:

  • Introduces drivers/rakuten_drive with auth/token refresh, request helpers, path utilities, and API response types.
  • Implements core driver operations (list/link/mkdir/move/rename/copy/remove/upload/quota).
  • Registers the new driver via drivers/all.go.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
drivers/rakuten_drive/driver.go Implements RakutenDrive driver operations, including S3 multipart upload and quota retrieval.
drivers/rakuten_drive/util.go Adds token refresh/JWT exp parsing, request wrapper, and path/time normalization helpers.
drivers/rakuten_drive/types.go Defines API response structs and a File object wrapper carrying extra metadata.
drivers/rakuten_drive/meta.go Defines driver config/addition schema and registers the driver.
drivers/all.go Imports the driver package to register it in the global driver set.

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

Comment on lines +408 to +424
// 4) check status
checkBody := base.Json{"key": initResp.UploadID}
for i := 0; i < 30; i++ {
var checkResp uploadCheckResp
_, err = d.newForestRequest(ctx, http.MethodPost, forestBase+"/v3/files/check", checkBody, &checkResp)
if err != nil {
return nil, err
}
if strings.EqualFold(checkResp.State, "complete") {
break
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(1 * time.Second):
}
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

In Put, the files/check polling loop breaks when state == complete, but if it never becomes complete within 30 tries the code still proceeds to complete/upload and returns success. Consider tracking whether completion was reached and returning a timeout/error (or handling explicit failure states) before continuing.

Copilot uses AI. Check for mistakes.
Comment on lines +527 to +529
if localDir != "" && !strings.HasSuffix(localDir, "/") && !strings.HasPrefix(localPath, "/") {
localPath = path.Join(localDir, name)
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This localPath adjustment branch appears unreachable: remotePath2LocalPath always returns a string starting with "/" (either "/" or "/..."), so !strings.HasPrefix(localPath, "/") will never be true. Consider removing this conditional or reworking it if a different path format is expected.

Suggested change
if localDir != "" && !strings.HasSuffix(localDir, "/") && !strings.HasPrefix(localPath, "/") {
localPath = path.Join(localDir, name)
}

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +130
if remoteDir == "/" {
remoteDir = ""
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

localPath2RemotePath(..., true) never returns "/" (it returns "" for root), so these remoteDir == "/" checks are dead code. Consider removing them to avoid confusion, or adjust localPath2RemotePath to return "/" for root consistently if that’s required by the API.

Suggested change
if remoteDir == "/" {
remoteDir = ""
}

Copilot uses AI. Check for mistakes.
Comment on lines +305 to +307
if remoteDir == "/" {
remoteDir = ""
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Same as above: localPath2RemotePath(..., true) returns "" (not "/") for root, so this remoteDir == "/" branch is unreachable and can be removed (or make root handling consistent in one place).

Suggested change
if remoteDir == "/" {
remoteDir = ""
}

Copilot uses AI. Check for mistakes.
if result != nil {
req.SetResult(result)
}
return req.Execute(method, url)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

newForestRequest returns req.Execute results without checking HTTP status codes. Resty does not treat 4xx/5xx as an error, so callers like MakeDir/Move/Rename/Remove/Put can incorrectly report success on API failures. Consider explicitly checking resp.IsError() / resp.StatusCode() >= 400 and returning an error (ideally including status + response body) before returning the response.

Suggested change
return req.Execute(method, url)
resp, err := req.Execute(method, url)
if err != nil {
return resp, err
}
if resp != nil && resp.IsError() {
return resp, fmt.Errorf("forest request %s %s failed with status %d: %s", method, url, resp.StatusCode(), strings.TrimSpace(resp.String()))
}
return resp, nil

Copilot uses AI. Check for mistakes.
Comment on lines +230 to +247
if resp.Key != "" {
checkBody := base.Json{"key": resp.Key}
for i := 0; i < 30; i++ {
var checkResp uploadCheckResp
_, err = d.newForestRequest(ctx, http.MethodPost, forestBase+"/v3/files/check", checkBody, &checkResp)
if err != nil {
return nil, err
}
if strings.EqualFold(checkResp.State, "complete") {
break
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(1 * time.Second):
}
}
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

In Copy, the async completion polling loop breaks after 30s but still returns a successful model.Object even if the operation never reaches state == complete. This can surface as phantom copies. After the loop, verify completion and return a timeout/error when the state is still not complete.

Copilot uses AI. Check for mistakes.
Comment on lines +281 to +300
if resp.Key == "" {
return nil
}
checkBody := base.Json{"key": resp.Key}
for i := 0; i < 30; i++ {
var checkResp uploadCheckResp
_, err = d.newForestRequest(ctx, http.MethodPost, forestBase+"/v3/files/check", checkBody, &checkResp)
if err != nil {
return err
}
if strings.EqualFold(checkResp.State, "complete") {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(1 * time.Second):
}
}
return nil
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Remove polls files/check up to 30 times but returns nil even if the operation never reaches state == complete (loop falls through). This can report a deletion as successful while it’s still pending/failed. Consider returning a timeout/error after the loop when completion wasn’t observed.

Copilot uses AI. Check for mistakes.
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.

5 participants