2023-06-23 09:36:40 +00:00
|
|
|
package dropbox
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"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"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (d *Dropbox) refreshToken() error {
|
|
|
|
url := d.base + "/oauth2/token"
|
|
|
|
if utils.SliceContains([]string{"", DefaultClientID}, d.ClientID) {
|
|
|
|
url = d.OauthTokenURL
|
|
|
|
}
|
|
|
|
var tokenResp TokenResp
|
|
|
|
resp, err := base.RestyClient.R().
|
|
|
|
//ForceContentType("application/x-www-form-urlencoded").
|
|
|
|
//SetBasicAuth(d.ClientID, d.ClientSecret).
|
|
|
|
SetFormData(map[string]string{
|
|
|
|
"grant_type": "refresh_token",
|
|
|
|
"refresh_token": d.RefreshToken,
|
|
|
|
"client_id": d.ClientID,
|
|
|
|
"client_secret": d.ClientSecret,
|
|
|
|
}).
|
|
|
|
Post(url)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Debugf("[dropbox] refresh token response: %s", resp.String())
|
|
|
|
if resp.StatusCode() != 200 {
|
|
|
|
return fmt.Errorf("failed to refresh token: %s", resp.String())
|
|
|
|
}
|
|
|
|
_ = utils.Json.UnmarshalFromString(resp.String(), &tokenResp)
|
|
|
|
d.AccessToken = tokenResp.AccessToken
|
|
|
|
op.MustSaveDriverStorage(d)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dropbox) request(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) {
|
|
|
|
req := base.RestyClient.R()
|
|
|
|
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
2024-01-24 09:03:50 +00:00
|
|
|
if d.RootNamespaceId != "" {
|
|
|
|
apiPathRootJson, err := utils.Json.MarshalToString(map[string]interface{}{
|
|
|
|
".tag": "root",
|
|
|
|
"root": d.RootNamespaceId,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.SetHeader("Dropbox-API-Path-Root", apiPathRootJson)
|
2023-06-23 09:36:40 +00:00
|
|
|
}
|
|
|
|
if callback != nil {
|
|
|
|
callback(req)
|
|
|
|
}
|
2024-01-24 09:03:50 +00:00
|
|
|
if method == http.MethodPost && req.Body != nil {
|
|
|
|
req.SetHeader("Content-Type", "application/json")
|
|
|
|
}
|
2023-06-23 09:36:40 +00:00
|
|
|
var e ErrorResp
|
|
|
|
req.SetError(&e)
|
|
|
|
res, err := req.Execute(method, d.base+uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
log.Debugf("[dropbox] request (%s) response: %s", uri, res.String())
|
|
|
|
isRetry := len(retry) > 0 && retry[0]
|
|
|
|
if res.StatusCode() != 200 {
|
|
|
|
body := res.String()
|
|
|
|
if !isRetry && (utils.SliceMeet([]string{"expired_access_token", "invalid_access_token", "authorization"}, body,
|
|
|
|
func(item string, v string) bool {
|
|
|
|
return strings.Contains(v, item)
|
|
|
|
}) || 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.Error, e.ErrorSummary)
|
|
|
|
}
|
|
|
|
return res.Body(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dropbox) list(ctx context.Context, data base.Json, isContinue bool) (*ListResp, error) {
|
|
|
|
var resp ListResp
|
|
|
|
uri := "/2/files/list_folder"
|
|
|
|
if isContinue {
|
|
|
|
uri += "/continue"
|
|
|
|
}
|
|
|
|
_, err := d.request(uri, http.MethodPost, func(req *resty.Request) {
|
2023-06-25 09:07:31 +00:00
|
|
|
req.SetContext(ctx).SetBody(data).SetResult(&resp)
|
2023-06-23 09:36:40 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dropbox) getFiles(ctx context.Context, path string) ([]File, error) {
|
|
|
|
hasMore := true
|
|
|
|
var marker string
|
|
|
|
res := make([]File, 0)
|
|
|
|
|
|
|
|
data := base.Json{
|
|
|
|
"include_deleted": false,
|
|
|
|
"include_has_explicit_shared_members": false,
|
|
|
|
"include_mounted_folders": false,
|
|
|
|
"include_non_downloadable_files": false,
|
|
|
|
"limit": 2000,
|
|
|
|
"path": path,
|
|
|
|
"recursive": false,
|
|
|
|
}
|
|
|
|
resp, err := d.list(ctx, data, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
marker = resp.Cursor
|
|
|
|
hasMore = resp.HasMore
|
|
|
|
res = append(res, resp.Entries...)
|
|
|
|
|
|
|
|
for hasMore {
|
|
|
|
data := base.Json{
|
|
|
|
"cursor": marker,
|
|
|
|
}
|
|
|
|
resp, err := d.list(ctx, data, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
marker = resp.Cursor
|
|
|
|
hasMore = resp.HasMore
|
|
|
|
res = append(res, resp.Entries...)
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dropbox) finishUploadSession(ctx context.Context, toPath string, offset int64, sessionId string) error {
|
|
|
|
url := d.contentBase + "/2/files/upload_session/finish"
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
req = req.WithContext(ctx)
|
|
|
|
req.Header.Set("Content-Type", "application/octet-stream")
|
|
|
|
req.Header.Set("Authorization", "Bearer "+d.AccessToken)
|
|
|
|
|
|
|
|
uploadFinishArgs := UploadFinishArgs{
|
|
|
|
Commit: struct {
|
|
|
|
Autorename bool `json:"autorename"`
|
|
|
|
Mode string `json:"mode"`
|
|
|
|
Mute bool `json:"mute"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
StrictConflict bool `json:"strict_conflict"`
|
|
|
|
}{
|
|
|
|
Autorename: true,
|
|
|
|
Mode: "add",
|
|
|
|
Mute: false,
|
|
|
|
Path: toPath,
|
|
|
|
StrictConflict: false,
|
|
|
|
},
|
|
|
|
Cursor: UploadCursor{
|
|
|
|
Offset: offset,
|
|
|
|
SessionID: sessionId,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
argsJson, err := utils.Json.MarshalToString(uploadFinishArgs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
req.Header.Set("Dropbox-API-Arg", argsJson)
|
|
|
|
|
|
|
|
res, err := base.HttpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to update file when finish session, err: %+v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_ = res.Body.Close()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dropbox) startUploadSession(ctx context.Context) (string, error) {
|
|
|
|
url := d.contentBase + "/2/files/upload_session/start"
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, nil)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
req = req.WithContext(ctx)
|
|
|
|
req.Header.Set("Content-Type", "application/octet-stream")
|
|
|
|
req.Header.Set("Authorization", "Bearer "+d.AccessToken)
|
|
|
|
req.Header.Set("Dropbox-API-Arg", "{\"close\":false}")
|
|
|
|
|
|
|
|
res, err := base.HttpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to update file when start session, err: %+v", err)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
|
|
sessionId := utils.Json.Get(body, "session_id").ToString()
|
|
|
|
|
|
|
|
_ = res.Body.Close()
|
|
|
|
return sessionId, nil
|
|
|
|
}
|