forked from TrueCloudLab/rclone
Factor REST library out of onedrive
This commit is contained in:
parent
8057d668bb
commit
7a24532224
2 changed files with 57 additions and 37 deletions
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/ncw/rclone/oauthutil"
|
"github.com/ncw/rclone/oauthutil"
|
||||||
"github.com/ncw/rclone/onedrive/api"
|
"github.com/ncw/rclone/onedrive/api"
|
||||||
"github.com/ncw/rclone/pacer"
|
"github.com/ncw/rclone/pacer"
|
||||||
|
"github.com/ncw/rclone/rest"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
@ -27,7 +28,8 @@ const (
|
||||||
rcloneClientSecret = "0+be4+jYw+7018HY6P3t/Izo+pTc+Yvt8+fy8NHU094="
|
rcloneClientSecret = "0+be4+jYw+7018HY6P3t/Izo+pTc+Yvt8+fy8NHU094="
|
||||||
minSleep = 10 * time.Millisecond
|
minSleep = 10 * time.Millisecond
|
||||||
maxSleep = 2 * time.Second
|
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
|
// Globals
|
||||||
|
@ -77,7 +79,7 @@ func init() {
|
||||||
// Fs represents a remote one drive
|
// Fs represents a remote one drive
|
||||||
type Fs struct {
|
type Fs struct {
|
||||||
name string // name of this remote
|
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
|
root string // the path we are working on
|
||||||
dirCache *dircache.DirCache // Map of directory path to directory id
|
dirCache *dircache.DirCache // Map of directory path to directory id
|
||||||
pacer *pacer.Pacer // pacer for API calls
|
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
|
// readMetaDataForPath reads the metadata from the path
|
||||||
func (f *Fs) readMetaDataForPath(path string) (info *api.Item, resp *http.Response, err error) {
|
func (f *Fs) readMetaDataForPath(path string) (info *api.Item, resp *http.Response, err error) {
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/drive/root:/" + replaceReservedChars(path),
|
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
|
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
|
// NewFs constructs an Fs from the path, container:path
|
||||||
func NewFs(name, root string) (fs.Fs, error) {
|
func NewFs(name, root string) (fs.Fs, error) {
|
||||||
root = parsePath(root)
|
root = parsePath(root)
|
||||||
|
@ -161,9 +177,10 @@ func NewFs(name, root string) (fs.Fs, error) {
|
||||||
f := &Fs{
|
f := &Fs{
|
||||||
name: name,
|
name: name,
|
||||||
root: root,
|
root: root,
|
||||||
srv: api.NewClient(oAuthClient),
|
srv: rest.NewClient(oAuthClient, rootURL),
|
||||||
pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant),
|
pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant),
|
||||||
}
|
}
|
||||||
|
f.srv.SetErrorHandler(errorHandler)
|
||||||
|
|
||||||
// Get rootID
|
// Get rootID
|
||||||
rootInfo, _, err := f.readMetaDataForPath("")
|
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)
|
// fs.Debug(f, "CreateDir(%q, %q)\n", pathID, leaf)
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
var info *api.Item
|
var info *api.Item
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Path: "/drive/items/" + pathID + "/children",
|
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) {
|
func (f *Fs) listAll(dirID string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) {
|
||||||
// Top parameter asks for bigger pages of data
|
// Top parameter asks for bigger pages of data
|
||||||
// https://dev.onedrive.com/odata/optional-query-parameters.htm
|
// https://dev.onedrive.com/odata/optional-query-parameters.htm
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/drive/items/" + dirID + "/children?top=1000",
|
Path: "/drive/items/" + dirID + "/children?top=1000",
|
||||||
}
|
}
|
||||||
|
@ -484,7 +501,7 @@ func (f *Fs) Mkdir() error {
|
||||||
|
|
||||||
// deleteObject removes an object by ID
|
// deleteObject removes an object by ID
|
||||||
func (f *Fs) deleteObject(id string) error {
|
func (f *Fs) deleteObject(id string) error {
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "DELETE",
|
Method: "DELETE",
|
||||||
Path: "/drive/items/" + id,
|
Path: "/drive/items/" + id,
|
||||||
NoResponse: true,
|
NoResponse: true,
|
||||||
|
@ -544,7 +561,7 @@ func (f *Fs) Precision() time.Duration {
|
||||||
func (f *Fs) waitForJob(location string, o *Object) error {
|
func (f *Fs) waitForJob(location string, o *Object) error {
|
||||||
deadline := time.Now().Add(fs.Config.Timeout)
|
deadline := time.Now().Add(fs.Config.Timeout)
|
||||||
for time.Now().Before(deadline) {
|
for time.Now().Before(deadline) {
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: location,
|
Path: location,
|
||||||
Absolute: true,
|
Absolute: true,
|
||||||
|
@ -560,7 +577,7 @@ func (f *Fs) waitForJob(location string, o *Object) error {
|
||||||
}
|
}
|
||||||
if resp.StatusCode == 202 {
|
if resp.StatusCode == 202 {
|
||||||
var status api.AsyncOperationStatus
|
var status api.AsyncOperationStatus
|
||||||
err = api.DecodeJSON(resp, &status)
|
err = rest.DecodeJSON(resp, &status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -569,7 +586,7 @@ func (f *Fs) waitForJob(location string, o *Object) error {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var info api.Item
|
var info api.Item
|
||||||
err = api.DecodeJSON(resp, &info)
|
err = rest.DecodeJSON(resp, &info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -608,7 +625,7 @@ func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the object
|
// Copy the object
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Path: "/drive/items/" + srcObj.id + "/action.copy",
|
Path: "/drive/items/" + srcObj.id + "/action.copy",
|
||||||
ExtraHeaders: map[string]string{"Prefer": "respond-async"},
|
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
|
// setModTime sets the modification time of the local fs object
|
||||||
func (o *Object) setModTime(modTime time.Time) (*api.Item, error) {
|
func (o *Object) setModTime(modTime time.Time) (*api.Item, error) {
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "PATCH",
|
Method: "PATCH",
|
||||||
Path: "/drive/root:/" + o.srvPath(),
|
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")
|
return nil, fmt.Errorf("Can't download no id")
|
||||||
}
|
}
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/drive/items/" + o.id + "/content",
|
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
|
// createUploadSession creates an upload session for the object
|
||||||
func (o *Object) createUploadSession() (response *api.CreateUploadResponse, err error) {
|
func (o *Object) createUploadSession() (response *api.CreateUploadResponse, err error) {
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Path: "/drive/root:/" + o.srvPath() + ":/upload.createSession",
|
Path: "/drive/root:/" + o.srvPath() + ":/upload.createSession",
|
||||||
}
|
}
|
||||||
|
@ -811,7 +828,7 @@ func (o *Object) createUploadSession() (response *api.CreateUploadResponse, err
|
||||||
// uploadFragment uploads a part
|
// uploadFragment uploads a part
|
||||||
func (o *Object) uploadFragment(url string, start int64, totalSize int64, buf []byte) (err error) {
|
func (o *Object) uploadFragment(url string, start int64, totalSize int64, buf []byte) (err error) {
|
||||||
bufSize := int64(len(buf))
|
bufSize := int64(len(buf))
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "PUT",
|
Method: "PUT",
|
||||||
Path: url,
|
Path: url,
|
||||||
Absolute: true,
|
Absolute: true,
|
||||||
|
@ -830,7 +847,7 @@ func (o *Object) uploadFragment(url string, start int64, totalSize int64, buf []
|
||||||
|
|
||||||
// cancelUploadSession cancels an upload session
|
// cancelUploadSession cancels an upload session
|
||||||
func (o *Object) cancelUploadSession(url string) (err error) {
|
func (o *Object) cancelUploadSession(url string) (err error) {
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "DELETE",
|
Method: "DELETE",
|
||||||
Path: url,
|
Path: url,
|
||||||
Absolute: true,
|
Absolute: true,
|
||||||
|
@ -903,7 +920,7 @@ func (o *Object) Update(in io.Reader, modTime time.Time, size int64) (err error)
|
||||||
if size <= int64(uploadCutoff) {
|
if size <= int64(uploadCutoff) {
|
||||||
// This is for less than 100 MB of content
|
// This is for less than 100 MB of content
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
opts := api.Opts{
|
opts := rest.Opts{
|
||||||
Method: "PUT",
|
Method: "PUT",
|
||||||
Path: "/drive/root:/" + o.srvPath() + ":/content",
|
Path: "/drive/root:/" + o.srvPath() + ":/content",
|
||||||
Body: in,
|
Body: in,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Package api implements the API for one drive
|
// Package rest implements a simple REST wrapper
|
||||||
package api
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -11,22 +11,34 @@ import (
|
||||||
"github.com/ncw/rclone/fs"
|
"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
|
// Client contains the info to sustain the API
|
||||||
type Client struct {
|
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
|
// 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{
|
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
|
// Opts contains parameters for Call, CallJSON etc
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
Method string
|
Method string
|
||||||
|
@ -69,7 +81,7 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
|
||||||
if opts.Absolute {
|
if opts.Absolute {
|
||||||
url = opts.Path
|
url = opts.Path
|
||||||
} else {
|
} else {
|
||||||
url = rootURL + opts.Path
|
url = api.rootURL + opts.Path
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(opts.Method, url, opts.Body)
|
req, err := http.NewRequest(opts.Method, url, opts.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -95,16 +107,7 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
// Decode error response
|
return resp, api.errorHandler(resp)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
if opts.NoResponse {
|
if opts.NoResponse {
|
||||||
return resp, resp.Body.Close()
|
return resp, resp.Body.Close()
|
Loading…
Add table
Reference in a new issue