|
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(). |
|
|
|
|
|
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) |
|
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) |
|
} |
|
if callback != nil { |
|
callback(req) |
|
} |
|
if method == http.MethodPost && req.Body != nil { |
|
req.SetHeader("Content-Type", "application/json") |
|
} |
|
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) { |
|
req.SetContext(ctx).SetBody(data).SetResult(&resp) |
|
}) |
|
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 |
|
} |
|
|