Skip to content

Commit 1c3607e

Browse files
committed
fix(baidu_netdisk): stream upload slices to avoid buffering whole parts in memory
1 parent aead76e commit 1c3607e

1 file changed

Lines changed: 84 additions & 28 deletions

File tree

drivers/baidu_netdisk/driver.go

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package baidu_netdisk
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/md5"
67
"encoding/hex"
78
"errors"
89
"fmt"
910
"io"
11+
"mime/multipart"
12+
"net/http"
1013
"net/url"
1114
"os"
1215
stdpath "path"
@@ -24,7 +27,6 @@ import (
2427
"github.com/alist-org/alist/v3/pkg/singleflight"
2528
"github.com/alist-org/alist/v3/pkg/utils"
2629
"github.com/avast/retry-go"
27-
"github.com/go-resty/resty/v2"
2830
log "github.com/sirupsen/logrus"
2931
)
3032

@@ -35,7 +37,6 @@ type BaiduNetdisk struct {
3537
uploadThread int
3638
vipType int // 会员类型,0普通用户(4G/4M)、1普通会员(10G/16M)、2超级会员(20G/32M)
3739

38-
upClient *resty.Client // 上传文件使用的http客户端
3940
uploadUrlG singleflight.Group[string]
4041
uploadUrlMu sync.RWMutex
4142
uploadUrl string // 上传域名
@@ -53,11 +54,6 @@ func (d *BaiduNetdisk) GetAddition() driver.Additional {
5354
}
5455

5556
func (d *BaiduNetdisk) Init(ctx context.Context) error {
56-
d.upClient = base.NewRestyClient().
57-
SetTimeout(UPLOAD_TIMEOUT).
58-
SetRetryCount(UPLOAD_RETRY_COUNT).
59-
SetRetryWaitTime(UPLOAD_RETRY_WAIT_TIME).
60-
SetRetryMaxWaitTime(UPLOAD_RETRY_MAX_WAIT_TIME)
6157
d.uploadThread, _ = strconv.Atoi(d.UploadThread)
6258
if d.uploadThread < 1 {
6359
d.uploadThread, d.UploadThread = 1, "1"
@@ -333,8 +329,7 @@ uploadLoop:
333329
"uploadid": precreateResp.Uploadid,
334330
"partseq": strconv.Itoa(partseq),
335331
}
336-
section := io.NewSectionReader(cacheReaderAt, offset, size)
337-
err := d.uploadSlice(ctx, uploadUrl, params, stream.GetName(), driver.NewLimitedUploadStream(ctx, section))
332+
err := d.uploadSlice(ctx, uploadUrl, params, stream.GetName(), cacheReaderAt, offset, size)
338333
if err != nil {
339334
return err
340335
}
@@ -426,29 +421,90 @@ func (d *BaiduNetdisk) precreate(ctx context.Context, path string, streamSize in
426421
return &precreateResp, nil
427422
}
428423

429-
func (d *BaiduNetdisk) uploadSlice(ctx context.Context, uploadUrl string, params map[string]string, fileName string, file io.Reader) error {
430-
res, err := d.upClient.R().
431-
SetContext(ctx).
432-
SetQueryParams(params).
433-
SetFileReader("file", fileName, file).
434-
Post(uploadUrl + "/rest/2.0/pcs/superfile2")
424+
// uploadSlice 流式上传分片,流式body无法重放,传输失败时基于SectionReader重建body重试
425+
func (d *BaiduNetdisk) uploadSlice(ctx context.Context, uploadUrl string, params map[string]string, fileName string, file io.ReaderAt, offset, size int64) error {
426+
var lastErr error
427+
for attempt := 0; attempt <= UPLOAD_RETRY_COUNT; attempt++ {
428+
if attempt > 0 {
429+
wait := UPLOAD_RETRY_WAIT_TIME << (attempt - 1)
430+
if wait > UPLOAD_RETRY_MAX_WAIT_TIME {
431+
wait = UPLOAD_RETRY_MAX_WAIT_TIME
432+
}
433+
select {
434+
case <-ctx.Done():
435+
return ctx.Err()
436+
case <-time.After(wait):
437+
}
438+
}
439+
body, err := d.doUploadSlice(ctx, uploadUrl, params, fileName, file, offset, size)
440+
if err != nil {
441+
lastErr = err
442+
continue
443+
}
444+
errCode := utils.Json.Get(body, "error_code").ToInt()
445+
errNo := utils.Json.Get(body, "errno").ToInt()
446+
respStr := string(body)
447+
lower := strings.ToLower(respStr)
448+
if strings.Contains(lower, "uploadid") &&
449+
(strings.Contains(lower, "invalid") || strings.Contains(lower, "expired") || strings.Contains(lower, "not found")) {
450+
return ErrUploadIDExpired
451+
}
452+
453+
if errCode != 0 || errNo != 0 {
454+
return errs.NewErr(errs.StreamIncomplete, "error uploading to baidu, response=%s", respStr)
455+
}
456+
return nil
457+
}
458+
return lastErr
459+
}
460+
461+
func (d *BaiduNetdisk) doUploadSlice(ctx context.Context, uploadUrl string, params map[string]string, fileName string, file io.ReaderAt, offset, size int64) ([]byte, error) {
462+
pr, pw := io.Pipe()
463+
mw := multipart.NewWriter(pw)
464+
go func() {
465+
part, err := mw.CreateFormFile("file", fileName)
466+
if err == nil {
467+
section := io.NewSectionReader(file, offset, size)
468+
_, err = utils.CopyWithBuffer(part, driver.NewLimitedUploadStream(ctx, section))
469+
}
470+
if err == nil {
471+
err = mw.Close()
472+
}
473+
_ = pw.CloseWithError(err)
474+
}()
475+
476+
reqCtx, cancel := context.WithTimeout(ctx, UPLOAD_TIMEOUT)
477+
defer cancel()
478+
req, err := http.NewRequestWithContext(reqCtx, http.MethodPost, uploadUrl+"/rest/2.0/pcs/superfile2", pr)
435479
if err != nil {
436-
return err
480+
_ = pr.Close()
481+
return nil, err
437482
}
438-
log.Debugln(res.RawResponse.Status + res.String())
439-
errCode := utils.Json.Get(res.Body(), "error_code").ToInt()
440-
errNo := utils.Json.Get(res.Body(), "errno").ToInt()
441-
respStr := res.String()
442-
lower := strings.ToLower(respStr)
443-
if strings.Contains(lower, "uploadid") &&
444-
(strings.Contains(lower, "invalid") || strings.Contains(lower, "expired") || strings.Contains(lower, "not found")) {
445-
return ErrUploadIDExpired
483+
query := req.URL.Query()
484+
for k, v := range params {
485+
query.Set(k, v)
486+
}
487+
req.URL.RawQuery = query.Encode()
488+
req.Header.Set("Content-Type", mw.FormDataContentType())
489+
req.Header.Set("User-Agent", base.UserAgent)
490+
var overhead bytes.Buffer
491+
ow := multipart.NewWriter(&overhead)
492+
_ = ow.SetBoundary(mw.Boundary())
493+
_, _ = ow.CreateFormFile("file", fileName)
494+
_ = ow.Close()
495+
req.ContentLength = int64(overhead.Len()) + size
496+
497+
res, err := base.HttpClient.Do(req)
498+
if err != nil {
499+
return nil, err
446500
}
447-
448-
if errCode != 0 || errNo != 0 {
449-
return errs.NewErr(errs.StreamIncomplete, "error uploading to baidu, response=%s", res.String())
501+
defer res.Body.Close()
502+
body, err := io.ReadAll(res.Body)
503+
if err != nil {
504+
return nil, err
450505
}
451-
return nil
506+
log.Debugln(res.Status + string(body))
507+
return body, nil
452508
}
453509

454510
var _ driver.Driver = (*BaiduNetdisk)(nil)

0 commit comments

Comments
 (0)