mirror of
https://github.com/rclone/rclone.git
synced 2024-11-22 08:23:47 +01:00
Factor REST library out of onedrive
This commit is contained in:
parent
8057d668bb
commit
7a24532224
@ -18,6 +18,7 @@ import (
|
||||
"github.com/ncw/rclone/oauthutil"
|
||||
"github.com/ncw/rclone/onedrive/api"
|
||||
"github.com/ncw/rclone/pacer"
|
||||
"github.com/ncw/rclone/rest"
|
||||
"github.com/spf13/pflag"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@ -27,7 +28,8 @@ const (
|
||||
rcloneClientSecret = "0+be4+jYw+7018HY6P3t/Izo+pTc+Yvt8+fy8NHU094="
|
||||
minSleep = 10 * time.Millisecond
|
||||
maxSleep = 2 * time.Second
|
||||
decayConstant = 2 // bigger for slower decay, exponential
|
||||
decayConstant = 2 // bigger for slower decay, exponential
|
||||
rootURL = "https://api.onedrive.com/v1.0" // root URL for requests
|
||||
)
|
||||
|
||||
// Globals
|
||||
@ -77,7 +79,7 @@ func init() {
|
||||
// Fs represents a remote one drive
|
||||
type Fs struct {
|
||||
name string // name of this remote
|
||||
srv *api.Client // the connection to the one drive server
|
||||
srv *rest.Client // the connection to the one drive server
|
||||
root string // the path we are working on
|
||||
dirCache *dircache.DirCache // Map of directory path to directory id
|
||||
pacer *pacer.Pacer // pacer for API calls
|
||||
@ -139,7 +141,7 @@ func shouldRetry(resp *http.Response, err error) (bool, error) {
|
||||
|
||||
// readMetaDataForPath reads the metadata from the path
|
||||
func (f *Fs) readMetaDataForPath(path string) (info *api.Item, resp *http.Response, err error) {
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "GET",
|
||||
Path: "/drive/root:/" + replaceReservedChars(path),
|
||||
}
|
||||
@ -150,6 +152,20 @@ func (f *Fs) readMetaDataForPath(path string) (info *api.Item, resp *http.Respon
|
||||
return info, resp, err
|
||||
}
|
||||
|
||||
// errorHandler parses a non 2xx error response into an error
|
||||
func errorHandler(resp *http.Response) error {
|
||||
// Decode error response
|
||||
errResponse := new(api.Error)
|
||||
err := rest.DecodeJSON(resp, &errResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errResponse.ErrorInfo.Code == "" {
|
||||
errResponse.ErrorInfo.Code = resp.Status
|
||||
}
|
||||
return errResponse
|
||||
}
|
||||
|
||||
// NewFs constructs an Fs from the path, container:path
|
||||
func NewFs(name, root string) (fs.Fs, error) {
|
||||
root = parsePath(root)
|
||||
@ -161,9 +177,10 @@ func NewFs(name, root string) (fs.Fs, error) {
|
||||
f := &Fs{
|
||||
name: name,
|
||||
root: root,
|
||||
srv: api.NewClient(oAuthClient),
|
||||
srv: rest.NewClient(oAuthClient, rootURL),
|
||||
pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant),
|
||||
}
|
||||
f.srv.SetErrorHandler(errorHandler)
|
||||
|
||||
// Get rootID
|
||||
rootInfo, _, err := f.readMetaDataForPath("")
|
||||
@ -266,7 +283,7 @@ func (f *Fs) CreateDir(pathID, leaf string) (newID string, err error) {
|
||||
// fs.Debug(f, "CreateDir(%q, %q)\n", pathID, leaf)
|
||||
var resp *http.Response
|
||||
var info *api.Item
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "POST",
|
||||
Path: "/drive/items/" + pathID + "/children",
|
||||
}
|
||||
@ -300,7 +317,7 @@ type listAllFn func(*api.Item) bool
|
||||
func (f *Fs) listAll(dirID string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) {
|
||||
// Top parameter asks for bigger pages of data
|
||||
// https://dev.onedrive.com/odata/optional-query-parameters.htm
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "GET",
|
||||
Path: "/drive/items/" + dirID + "/children?top=1000",
|
||||
}
|
||||
@ -484,7 +501,7 @@ func (f *Fs) Mkdir() error {
|
||||
|
||||
// deleteObject removes an object by ID
|
||||
func (f *Fs) deleteObject(id string) error {
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "DELETE",
|
||||
Path: "/drive/items/" + id,
|
||||
NoResponse: true,
|
||||
@ -544,7 +561,7 @@ func (f *Fs) Precision() time.Duration {
|
||||
func (f *Fs) waitForJob(location string, o *Object) error {
|
||||
deadline := time.Now().Add(fs.Config.Timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "GET",
|
||||
Path: location,
|
||||
Absolute: true,
|
||||
@ -560,7 +577,7 @@ func (f *Fs) waitForJob(location string, o *Object) error {
|
||||
}
|
||||
if resp.StatusCode == 202 {
|
||||
var status api.AsyncOperationStatus
|
||||
err = api.DecodeJSON(resp, &status)
|
||||
err = rest.DecodeJSON(resp, &status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -569,7 +586,7 @@ func (f *Fs) waitForJob(location string, o *Object) error {
|
||||
}
|
||||
} else {
|
||||
var info api.Item
|
||||
err = api.DecodeJSON(resp, &info)
|
||||
err = rest.DecodeJSON(resp, &info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -608,7 +625,7 @@ func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
|
||||
}
|
||||
|
||||
// Copy the object
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "POST",
|
||||
Path: "/drive/items/" + srcObj.id + "/action.copy",
|
||||
ExtraHeaders: map[string]string{"Prefer": "respond-async"},
|
||||
@ -741,7 +758,7 @@ func (o *Object) ModTime() time.Time {
|
||||
|
||||
// setModTime sets the modification time of the local fs object
|
||||
func (o *Object) setModTime(modTime time.Time) (*api.Item, error) {
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "PATCH",
|
||||
Path: "/drive/root:/" + o.srvPath(),
|
||||
}
|
||||
@ -780,7 +797,7 @@ func (o *Object) Open() (in io.ReadCloser, err error) {
|
||||
return nil, fmt.Errorf("Can't download no id")
|
||||
}
|
||||
var resp *http.Response
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "GET",
|
||||
Path: "/drive/items/" + o.id + "/content",
|
||||
}
|
||||
@ -796,7 +813,7 @@ func (o *Object) Open() (in io.ReadCloser, err error) {
|
||||
|
||||
// createUploadSession creates an upload session for the object
|
||||
func (o *Object) createUploadSession() (response *api.CreateUploadResponse, err error) {
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "POST",
|
||||
Path: "/drive/root:/" + o.srvPath() + ":/upload.createSession",
|
||||
}
|
||||
@ -811,7 +828,7 @@ func (o *Object) createUploadSession() (response *api.CreateUploadResponse, err
|
||||
// uploadFragment uploads a part
|
||||
func (o *Object) uploadFragment(url string, start int64, totalSize int64, buf []byte) (err error) {
|
||||
bufSize := int64(len(buf))
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "PUT",
|
||||
Path: url,
|
||||
Absolute: true,
|
||||
@ -830,7 +847,7 @@ func (o *Object) uploadFragment(url string, start int64, totalSize int64, buf []
|
||||
|
||||
// cancelUploadSession cancels an upload session
|
||||
func (o *Object) cancelUploadSession(url string) (err error) {
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "DELETE",
|
||||
Path: url,
|
||||
Absolute: true,
|
||||
@ -903,7 +920,7 @@ func (o *Object) Update(in io.Reader, modTime time.Time, size int64) (err error)
|
||||
if size <= int64(uploadCutoff) {
|
||||
// This is for less than 100 MB of content
|
||||
var resp *http.Response
|
||||
opts := api.Opts{
|
||||
opts := rest.Opts{
|
||||
Method: "PUT",
|
||||
Path: "/drive/root:/" + o.srvPath() + ":/content",
|
||||
Body: in,
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Package api implements the API for one drive
|
||||
package api
|
||||
// Package rest implements a simple REST wrapper
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -11,22 +11,34 @@ import (
|
||||
"github.com/ncw/rclone/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
rootURL = "https://api.onedrive.com/v1.0" // root URL for requests
|
||||
)
|
||||
|
||||
// Client contains the info to sustain the API
|
||||
type Client struct {
|
||||
c *http.Client
|
||||
c *http.Client
|
||||
rootURL string
|
||||
errorHandler func(resp *http.Response) error
|
||||
}
|
||||
|
||||
// NewClient takes an oauth http.Client and makes a new api instance
|
||||
func NewClient(c *http.Client) *Client {
|
||||
func NewClient(c *http.Client, rootURL string) *Client {
|
||||
return &Client{
|
||||
c: c,
|
||||
c: c,
|
||||
rootURL: rootURL,
|
||||
errorHandler: defaultErrorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// defaultErrorHandler doesn't attempt to parse the http body
|
||||
func defaultErrorHandler(resp *http.Response) (err error) {
|
||||
defer checkClose(resp.Body, &err)
|
||||
return fmt.Errorf("HTTP error %v (%v) returned", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
// SetErrorHandler sets the handler to decode an error response when
|
||||
// the HTTP status code is not 2xx. The handler should close resp.Body.
|
||||
func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) {
|
||||
api.errorHandler = fn
|
||||
}
|
||||
|
||||
// Opts contains parameters for Call, CallJSON etc
|
||||
type Opts struct {
|
||||
Method string
|
||||
@ -69,7 +81,7 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
|
||||
if opts.Absolute {
|
||||
url = opts.Path
|
||||
} else {
|
||||
url = rootURL + opts.Path
|
||||
url = api.rootURL + opts.Path
|
||||
}
|
||||
req, err := http.NewRequest(opts.Method, url, opts.Body)
|
||||
if err != nil {
|
||||
@ -95,16 +107,7 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
// Decode error response
|
||||
errResponse := new(Error)
|
||||
err = DecodeJSON(resp, &errResponse)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if errResponse.ErrorInfo.Code == "" {
|
||||
errResponse.ErrorInfo.Code = resp.Status
|
||||
}
|
||||
return resp, errResponse
|
||||
return resp, api.errorHandler(resp)
|
||||
}
|
||||
if opts.NoResponse {
|
||||
return resp, resp.Body.Close()
|
Loading…
Reference in New Issue
Block a user