mirror of https://github.com/Xhofe/alist
176 lines
4.7 KiB
Go
176 lines
4.7 KiB
Go
package aliyundrive_open
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
"github.com/alist-org/alist/v3/internal/op"
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
|
"github.com/go-resty/resty/v2"
|
|
)
|
|
|
|
// do others that not defined in Driver interface
|
|
|
|
func (d *AliyundriveOpen) refreshToken() error {
|
|
url := d.base + "/oauth/access_token"
|
|
if d.OauthTokenURL != "" && d.ClientID == "" {
|
|
url = d.OauthTokenURL
|
|
}
|
|
var resp base.TokenResp
|
|
var e ErrResp
|
|
_, err := base.RestyClient.R().
|
|
ForceContentType("application/json").
|
|
SetBody(base.Json{
|
|
"client_id": d.ClientID,
|
|
"client_secret": d.ClientSecret,
|
|
"grant_type": "refresh_token",
|
|
"refresh_token": d.RefreshToken,
|
|
}).
|
|
SetResult(&resp).
|
|
SetError(&e).
|
|
Post(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.Code != "" {
|
|
return fmt.Errorf("failed to refresh token: %s", e.Message)
|
|
}
|
|
if resp.RefreshToken == "" {
|
|
return errors.New("failed to refresh token: refresh token is empty")
|
|
}
|
|
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
|
|
op.MustSaveDriverStorage(d)
|
|
return nil
|
|
}
|
|
|
|
func (d *AliyundriveOpen) request(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) {
|
|
req := base.RestyClient.R()
|
|
// TODO check whether access_token is expired
|
|
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
|
if method == http.MethodPost {
|
|
req.SetHeader("Content-Type", "application/json")
|
|
}
|
|
if callback != nil {
|
|
callback(req)
|
|
}
|
|
var e ErrResp
|
|
req.SetError(&e)
|
|
res, err := req.Execute(method, d.base+uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
isRetry := len(retry) > 0 && retry[0]
|
|
if e.Code != "" {
|
|
if !isRetry && (utils.SliceContains([]string{"AccessTokenInvalid", "AccessTokenExpired", "I400JD"}, e.Code) || d.AccessToken == "") {
|
|
err = d.refreshToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return d.request(uri, method, callback, true)
|
|
}
|
|
return nil, fmt.Errorf("%s:%s", e.Code, e.Message)
|
|
}
|
|
return res.Body(), nil
|
|
}
|
|
|
|
func (d *AliyundriveOpen) list(ctx context.Context, data base.Json) (*Files, error) {
|
|
var resp Files
|
|
_, err := d.request("/adrive/v1.0/openFile/list", http.MethodPost, func(req *resty.Request) {
|
|
req.SetBody(data).SetResult(&resp)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
func (d *AliyundriveOpen) getFiles(ctx context.Context, fileId string) ([]File, error) {
|
|
marker := "first"
|
|
res := make([]File, 0)
|
|
for marker != "" {
|
|
if marker == "first" {
|
|
marker = ""
|
|
}
|
|
data := base.Json{
|
|
"drive_id": d.DriveId,
|
|
"limit": 200,
|
|
"marker": marker,
|
|
"order_by": d.OrderBy,
|
|
"order_direction": d.OrderDirection,
|
|
"parent_file_id": fileId,
|
|
//"category": "",
|
|
//"type": "",
|
|
//"video_thumbnail_time": 120000,
|
|
//"video_thumbnail_width": 480,
|
|
//"image_thumbnail_width": 480,
|
|
}
|
|
resp, err := d.limitList(ctx, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
marker = resp.NextMarker
|
|
res = append(res, resp.Items...)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func makePartInfos(size int) []base.Json {
|
|
partInfoList := make([]base.Json, size)
|
|
for i := 0; i < size; i++ {
|
|
partInfoList[i] = base.Json{"part_number": 1 + i}
|
|
}
|
|
return partInfoList
|
|
}
|
|
|
|
func (d *AliyundriveOpen) getUploadUrl(count int, fileId, uploadId string) ([]PartInfo, error) {
|
|
partInfoList := makePartInfos(count)
|
|
var resp CreateResp
|
|
_, err := d.request("/adrive/v1.0/openFile/getUploadUrl", http.MethodPost, func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"drive_id": d.DriveId,
|
|
"file_id": fileId,
|
|
"part_info_list": partInfoList,
|
|
"upload_id": uploadId,
|
|
}).SetResult(&resp)
|
|
})
|
|
return resp.PartInfoList, err
|
|
}
|
|
|
|
func (d *AliyundriveOpen) uploadPart(ctx context.Context, i, count int, reader *utils.MultiReadable, resp *CreateResp, retry bool) error {
|
|
partInfo := resp.PartInfoList[i-1]
|
|
uploadUrl := partInfo.UploadUrl
|
|
if d.InternalUpload {
|
|
uploadUrl = strings.ReplaceAll(uploadUrl, "https://cn-beijing-data.aliyundrive.net/", "http://ccp-bj29-bj-1592982087.oss-cn-beijing-internal.aliyuncs.com/")
|
|
}
|
|
req, err := http.NewRequest("PUT", uploadUrl, reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req = req.WithContext(ctx)
|
|
res, err := base.HttpClient.Do(req)
|
|
if err != nil {
|
|
if retry {
|
|
reader.Reset()
|
|
return d.uploadPart(ctx, i, count, reader, resp, false)
|
|
}
|
|
return err
|
|
}
|
|
res.Body.Close()
|
|
if retry && res.StatusCode == http.StatusForbidden {
|
|
resp.PartInfoList, err = d.getUploadUrl(count, resp.FileId, resp.UploadId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reader.Reset()
|
|
return d.uploadPart(ctx, i, count, reader, resp, false)
|
|
}
|
|
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusConflict {
|
|
return fmt.Errorf("upload status: %d", res.StatusCode)
|
|
}
|
|
return nil
|
|
}
|