| | 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 |
| | } |
| |
|