11package baidu_netdisk
22
33import (
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
5556func (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
454510var _ driver.Driver = (* BaiduNetdisk )(nil )
0 commit comments