Skip to content

Commit 39dcf9b

Browse files
Y-ASLantKirCute
andauthored
feat(onedrive): support frontend direct upload (#1532)
* OneDrive添加直连上传 * refactor * fix: duplicate root path join --------- Co-authored-by: KirCute <951206789@qq.com>
1 parent 25f38df commit 39dcf9b

File tree

16 files changed

+265
-38
lines changed

16 files changed

+265
-38
lines changed

drivers/onedrive/driver.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,19 @@ func (d *Onedrive) GetDetails(ctx context.Context) (*model.StorageDetails, error
236236
}, nil
237237
}
238238

239+
func (d *Onedrive) GetDirectUploadTools() []string {
240+
if !d.EnableDirectUpload {
241+
return nil
242+
}
243+
return []string{"HttpDirect"}
244+
}
245+
246+
// GetDirectUploadInfo returns the direct upload info for OneDrive
247+
func (d *Onedrive) GetDirectUploadInfo(ctx context.Context, _ string, dstDir model.Obj, fileName string, _ int64) (any, error) {
248+
if !d.EnableDirectUpload {
249+
return nil, errs.NotImplement
250+
}
251+
return d.getDirectUploadInfo(ctx, path.Join(dstDir.GetPath(), fileName))
252+
}
253+
239254
var _ driver.Driver = (*Onedrive)(nil)

drivers/onedrive/meta.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Addition struct {
1919
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
2020
CustomHost string `json:"custom_host" help:"Custom host for onedrive download link"`
2121
DisableDiskUsage bool `json:"disable_disk_usage" default:"false"`
22+
EnableDirectUpload bool `json:"enable_direct_upload" default:"false" help:"Enable direct upload from client to OneDrive"`
2223
}
2324

2425
var config = driver.Config{

drivers/onedrive/util.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (d *Onedrive) _refreshToken() error {
133133
return nil
134134
}
135135

136-
func (d *Onedrive) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
136+
func (d *Onedrive) Request(url string, method string, callback base.ReqCallback, resp interface{}, noRetry ...bool) ([]byte, error) {
137137
if d.ref != nil {
138138
return d.ref.Request(url, method, callback, resp)
139139
}
@@ -152,7 +152,7 @@ func (d *Onedrive) Request(url string, method string, callback base.ReqCallback,
152152
return nil, err
153153
}
154154
if e.Error.Code != "" {
155-
if e.Error.Code == "InvalidAuthenticationToken" {
155+
if e.Error.Code == "InvalidAuthenticationToken" && !utils.IsBool(noRetry...) {
156156
err = d.refreshToken()
157157
if err != nil {
158158
return nil, err
@@ -310,9 +310,36 @@ func (d *Onedrive) getDrive(ctx context.Context) (*DriveResp, error) {
310310
var resp DriveResp
311311
_, err := d.Request(api, http.MethodGet, func(req *resty.Request) {
312312
req.SetContext(ctx)
313-
}, &resp)
313+
}, &resp, true)
314314
if err != nil {
315315
return nil, err
316316
}
317317
return &resp, nil
318318
}
319+
320+
func (d *Onedrive) getDirectUploadInfo(ctx context.Context, path string) (*model.HttpDirectUploadInfo, error) {
321+
// Create upload session
322+
url := d.GetMetaUrl(false, path) + "/createUploadSession"
323+
metadata := map[string]any{
324+
"item": map[string]any{
325+
"@microsoft.graph.conflictBehavior": "rename",
326+
},
327+
}
328+
329+
res, err := d.Request(url, http.MethodPost, func(req *resty.Request) {
330+
req.SetBody(metadata).SetContext(ctx)
331+
}, nil)
332+
if err != nil {
333+
return nil, err
334+
}
335+
336+
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
337+
if uploadUrl == "" {
338+
return nil, fmt.Errorf("failed to get upload URL from response")
339+
}
340+
return &model.HttpDirectUploadInfo{
341+
UploadURL: uploadUrl,
342+
ChunkSize: d.ChunkSize * 1024 * 1024, // Convert MB to bytes
343+
Method: "PUT",
344+
}, nil
345+
}

drivers/onedrive_app/driver.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,18 @@ func (d *OnedriveAPP) GetDetails(ctx context.Context) (*model.StorageDetails, er
222222
}, nil
223223
}
224224

225+
func (d *OnedriveAPP) GetDirectUploadTools() []string {
226+
if !d.EnableDirectUpload {
227+
return nil
228+
}
229+
return []string{"HttpDirect"}
230+
}
231+
232+
func (d *OnedriveAPP) GetDirectUploadInfo(ctx context.Context, _ string, dstDir model.Obj, fileName string, _ int64) (any, error) {
233+
if !d.EnableDirectUpload {
234+
return nil, errs.NotImplement
235+
}
236+
return d.getDirectUploadInfo(ctx, path.Join(dstDir.GetPath(), fileName))
237+
}
238+
225239
var _ driver.Driver = (*OnedriveAPP)(nil)

drivers/onedrive_app/meta.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import (
77

88
type Addition struct {
99
driver.RootPath
10-
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
11-
ClientID string `json:"client_id" required:"true"`
12-
ClientSecret string `json:"client_secret" required:"true"`
13-
TenantID string `json:"tenant_id"`
14-
Email string `json:"email"`
15-
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
16-
CustomHost string `json:"custom_host" help:"Custom host for onedrive download link"`
17-
DisableDiskUsage bool `json:"disable_disk_usage" default:"false"`
10+
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
11+
ClientID string `json:"client_id" required:"true"`
12+
ClientSecret string `json:"client_secret" required:"true"`
13+
TenantID string `json:"tenant_id"`
14+
Email string `json:"email"`
15+
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
16+
CustomHost string `json:"custom_host" help:"Custom host for onedrive download link"`
17+
DisableDiskUsage bool `json:"disable_disk_usage" default:"false"`
18+
EnableDirectUpload bool `json:"enable_direct_upload" default:"false" help:"Enable direct upload from client to OneDrive"`
1819
}
1920

2021
var config = driver.Config{

drivers/onedrive_app/util.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (d *OnedriveAPP) _accessToken() error {
8888
return nil
8989
}
9090

91-
func (d *OnedriveAPP) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
91+
func (d *OnedriveAPP) Request(url string, method string, callback base.ReqCallback, resp interface{}, noRetry ...bool) ([]byte, error) {
9292
req := base.RestyClient.R()
9393
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
9494
if callback != nil {
@@ -104,7 +104,7 @@ func (d *OnedriveAPP) Request(url string, method string, callback base.ReqCallba
104104
return nil, err
105105
}
106106
if e.Error.Code != "" {
107-
if e.Error.Code == "InvalidAuthenticationToken" {
107+
if e.Error.Code == "InvalidAuthenticationToken" && !utils.IsBool(noRetry...) {
108108
err = d.accessToken()
109109
if err != nil {
110110
return nil, err
@@ -216,9 +216,36 @@ func (d *OnedriveAPP) getDrive(ctx context.Context) (*DriveResp, error) {
216216
var resp DriveResp
217217
_, err := d.Request(api, http.MethodGet, func(req *resty.Request) {
218218
req.SetContext(ctx)
219-
}, &resp)
219+
}, &resp, true)
220220
if err != nil {
221221
return nil, err
222222
}
223223
return &resp, nil
224224
}
225+
226+
func (d *OnedriveAPP) getDirectUploadInfo(ctx context.Context, path string) (*model.HttpDirectUploadInfo, error) {
227+
// Create upload session
228+
url := d.GetMetaUrl(false, path) + "/createUploadSession"
229+
metadata := map[string]any{
230+
"item": map[string]any{
231+
"@microsoft.graph.conflictBehavior": "rename",
232+
},
233+
}
234+
235+
res, err := d.Request(url, http.MethodPost, func(req *resty.Request) {
236+
req.SetBody(metadata).SetContext(ctx)
237+
}, nil)
238+
if err != nil {
239+
return nil, err
240+
}
241+
242+
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
243+
if uploadUrl == "" {
244+
return nil, fmt.Errorf("failed to get upload URL from response")
245+
}
246+
return &model.HttpDirectUploadInfo{
247+
UploadURL: uploadUrl,
248+
ChunkSize: d.ChunkSize * 1024 * 1024, // Convert MB to bytes
249+
Method: "PUT",
250+
}, nil
251+
}

internal/driver/driver.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,12 @@ type LinkCacheModeResolver interface {
218218
// ResolveLinkCacheMode returns the LinkCacheMode for the given path.
219219
ResolveLinkCacheMode(path string) LinkCacheMode
220220
}
221+
222+
type DirectUploader interface {
223+
// GetDirectUploadTools returns available frontend-direct upload tools
224+
GetDirectUploadTools() []string
225+
// GetDirectUploadInfo returns the information needed for direct upload from client to storage
226+
// actualPath is the path relative to the storage root (after removing mount path prefix)
227+
// return errs.NotImplement if the driver does not support the given direct upload tool
228+
GetDirectUploadInfo(ctx context.Context, tool string, dstDir model.Obj, fileName string, fileSize int64) (any, error)
229+
}

internal/errs/object.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import (
77
)
88

99
var (
10-
ObjectNotFound = errors.New("object not found")
11-
NotFolder = errors.New("not a folder")
12-
NotFile = errors.New("not a file")
10+
ObjectNotFound = errors.New("object not found")
11+
ObjectAlreadyExists = errors.New("object already exists")
12+
NotFolder = errors.New("not a folder")
13+
NotFile = errors.New("not a file")
1314
)
1415

1516
func IsObjectNotFound(err error) bool {

internal/fs/fs.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ func GetStorage(path string, args *GetStoragesArgs) (driver.Driver, error) {
167167
return storageDriver, nil
168168
}
169169

170+
func GetStorageAndActualPath(path string) (driver.Driver, string, error) {
171+
return op.GetStorageAndActualPath(path)
172+
}
173+
174+
func GetByActualPath(ctx context.Context, storage driver.Driver, actualPath string) (model.Obj, error) {
175+
return op.Get(ctx, storage, actualPath)
176+
}
177+
170178
func Other(ctx context.Context, args model.FsOtherArgs) (interface{}, error) {
171179
res, err := other(ctx, args)
172180
if err != nil {
@@ -190,3 +198,11 @@ func PutURL(ctx context.Context, path, dstName, urlStr string) error {
190198
}
191199
return op.PutURL(ctx, storage, dstDirActualPath, dstName, urlStr)
192200
}
201+
202+
func GetDirectUploadInfo(ctx context.Context, tool, path, dstName string, fileSize int64) (any, error) {
203+
info, err := getDirectUploadInfo(ctx, tool, path, dstName, fileSize)
204+
if err != nil {
205+
log.Errorf("failed get %s direct upload info for %s(%d bytes): %+v", path, dstName, fileSize, err)
206+
}
207+
return info, err
208+
}

internal/fs/put.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,11 @@ func putDirectly(ctx context.Context, dstDirPath string, file model.FileStreamer
105105
}
106106
return op.Put(ctx, storage, dstDirActualPath, file, nil, lazyCache...)
107107
}
108+
109+
func getDirectUploadInfo(ctx context.Context, tool, dstDirPath, dstName string, fileSize int64) (any, error) {
110+
storage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
111+
if err != nil {
112+
return nil, errors.WithMessage(err, "failed get storage")
113+
}
114+
return op.GetDirectUploadInfo(ctx, tool, storage, dstDirActualPath, dstName, fileSize)
115+
}

0 commit comments

Comments
 (0)