mirror of
https://github.com/rclone/rclone.git
synced 2024-12-23 15:38:57 +01:00
onedrive: implement Copy
This commit is contained in:
parent
be6115fbfa
commit
8f2999b6af
@ -37,6 +37,7 @@ type Opts struct {
|
|||||||
ContentType string
|
ContentType string
|
||||||
ContentLength *int64
|
ContentLength *int64
|
||||||
ContentRange string
|
ContentRange string
|
||||||
|
ExtraHeaders map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkClose is a utility function used to check the return from
|
// checkClose is a utility function used to check the return from
|
||||||
@ -48,8 +49,8 @@ func checkClose(c io.Closer, err *error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeJSON decodes resp.Body into json
|
// DecodeJSON decodes resp.Body into result
|
||||||
func (api *Client) decodeJSON(resp *http.Response, result interface{}) (err error) {
|
func DecodeJSON(resp *http.Response, result interface{}) (err error) {
|
||||||
defer checkClose(resp.Body, &err)
|
defer checkClose(resp.Body, &err)
|
||||||
decoder := json.NewDecoder(resp.Body)
|
decoder := json.NewDecoder(resp.Body)
|
||||||
return decoder.Decode(result)
|
return decoder.Decode(result)
|
||||||
@ -83,6 +84,11 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
|
|||||||
if opts.ContentRange != "" {
|
if opts.ContentRange != "" {
|
||||||
req.Header.Add("Content-Range", opts.ContentRange)
|
req.Header.Add("Content-Range", opts.ContentRange)
|
||||||
}
|
}
|
||||||
|
if opts.ExtraHeaders != nil {
|
||||||
|
for k, v := range opts.ExtraHeaders {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
req.Header.Add("User-Agent", fs.UserAgent)
|
req.Header.Add("User-Agent", fs.UserAgent)
|
||||||
resp, err = api.c.Do(req)
|
resp, err = api.c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -91,10 +97,13 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
// Decode error response
|
// Decode error response
|
||||||
errResponse := new(Error)
|
errResponse := new(Error)
|
||||||
err = api.decodeJSON(resp, &errResponse)
|
err = DecodeJSON(resp, &errResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
if errResponse.ErrorInfo.Code == "" {
|
||||||
|
errResponse.ErrorInfo.Code = resp.Status
|
||||||
|
}
|
||||||
return resp, errResponse
|
return resp, errResponse
|
||||||
}
|
}
|
||||||
if opts.NoResponse {
|
if opts.NoResponse {
|
||||||
@ -103,7 +112,7 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallJSON runs Call and decodes the body as a JSON object into result
|
// CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
|
||||||
//
|
//
|
||||||
// If request is not nil then it will be JSON encoded as the body of the request
|
// If request is not nil then it will be JSON encoded as the body of the request
|
||||||
//
|
//
|
||||||
@ -124,6 +133,9 @@ func (api *Client) CallJSON(opts *Opts, request interface{}, response interface{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
err = api.decodeJSON(resp, response)
|
if opts.NoResponse {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
err = DecodeJSON(resp, response)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
@ -189,3 +189,25 @@ type UploadFragmentResponse struct {
|
|||||||
ExpirationDateTime Timestamp `json:"expirationDateTime"` // "2015-01-29T09:21:55.523Z",
|
ExpirationDateTime Timestamp `json:"expirationDateTime"` // "2015-01-29T09:21:55.523Z",
|
||||||
NextExpectedRanges []string `json:"nextExpectedRanges"` // ["0-"]
|
NextExpectedRanges []string `json:"nextExpectedRanges"` // ["0-"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CopyItemRequest is the request to copy an item object
|
||||||
|
//
|
||||||
|
// Note: The parentReference should include either an id or path but
|
||||||
|
// not both. If both are included, they need to reference the same
|
||||||
|
// item or an error will occur.
|
||||||
|
type CopyItemRequest struct {
|
||||||
|
ParentReference ItemReference `json:"parentReference"` // Reference to the parent item the copy will be created in.
|
||||||
|
Name *string `json:"name"` // Optional The new name for the copy. If this isn't provided, the same name will be used as the original.
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncOperationStatus provides information on the status of a asynchronous job progress.
|
||||||
|
//
|
||||||
|
// The following API calls return AsyncOperationStatus resources:
|
||||||
|
//
|
||||||
|
// Copy Item
|
||||||
|
// Upload From URL
|
||||||
|
type AsyncOperationStatus struct {
|
||||||
|
Operation string `json:"operation"` // The type of job being run.
|
||||||
|
PercentageComplete float64 `json:"percentageComplete"` // An float value between 0 and 100 that indicates the percentage complete.
|
||||||
|
Status string `json:"status"` // A string value that maps to an enumeration of possible values about the status of the job. "notStarted | inProgress | completed | updating | failed | deletePending | deleteFailed | waiting"
|
||||||
|
}
|
||||||
|
@ -271,7 +271,7 @@ func (f *Fs) CreateDir(pathID, leaf string) (newID string, err error) {
|
|||||||
Path: "/drive/items/" + pathID + "/children",
|
Path: "/drive/items/" + pathID + "/children",
|
||||||
}
|
}
|
||||||
mkdir := api.CreateItemRequest{
|
mkdir := api.CreateItemRequest{
|
||||||
Name: leaf,
|
Name: replaceReservedChars(leaf),
|
||||||
ConflictBehavior: "fail",
|
ConflictBehavior: "fail",
|
||||||
}
|
}
|
||||||
err = f.pacer.Call(func() (bool, error) {
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
@ -444,22 +444,36 @@ func (f *Fs) ListDir() fs.DirChan {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates from the parameters passed in a half finished Object which
|
||||||
|
// must have setMetaData called on it
|
||||||
|
//
|
||||||
|
// Returns the object, leaf, directoryID and error
|
||||||
|
//
|
||||||
|
// Used to create new objects
|
||||||
|
func (f *Fs) createObject(remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) {
|
||||||
|
// Create the directory for the object if it doesn't exist
|
||||||
|
leaf, directoryID, err = f.dirCache.FindPath(remote, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, leaf, directoryID, err
|
||||||
|
}
|
||||||
|
// Temporary Object under construction
|
||||||
|
o = &Object{
|
||||||
|
fs: f,
|
||||||
|
remote: remote,
|
||||||
|
}
|
||||||
|
return o, leaf, directoryID, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Put the object into the container
|
// Put the object into the container
|
||||||
//
|
//
|
||||||
// Copy the reader in to the new object which is returned
|
// Copy the reader in to the new object which is returned
|
||||||
//
|
//
|
||||||
// The new object may have been created if an error is returned
|
// The new object may have been created if an error is returned
|
||||||
func (f *Fs) Put(in io.Reader, remote string, modTime time.Time, size int64) (fs.Object, error) {
|
func (f *Fs) Put(in io.Reader, remote string, modTime time.Time, size int64) (fs.Object, error) {
|
||||||
// Create the directory for the object if it doesn't exist
|
o, _, _, err := f.createObject(remote, modTime, size)
|
||||||
_, _, err := f.dirCache.FindPath(remote, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Temporary Object under construction
|
|
||||||
o := &Object{
|
|
||||||
fs: f,
|
|
||||||
remote: remote,
|
|
||||||
}
|
|
||||||
return o, o.Update(in, modTime, size)
|
return o, o.Update(in, modTime, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,6 +540,47 @@ func (f *Fs) Precision() time.Duration {
|
|||||||
return time.Second
|
return time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitForJob waits for the job with status in url to complete
|
||||||
|
func (f *Fs) waitForJob(location string, o *Object) error {
|
||||||
|
deadline := time.Now().Add(fs.Config.Timeout)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
opts := api.Opts{
|
||||||
|
Method: "GET",
|
||||||
|
Path: location,
|
||||||
|
Absolute: true,
|
||||||
|
}
|
||||||
|
var resp *http.Response
|
||||||
|
var err error
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err = f.srv.Call(&opts)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 202 {
|
||||||
|
var status api.AsyncOperationStatus
|
||||||
|
err = api.DecodeJSON(resp, &status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if status.Status == "failed" || status.Status == "deleteFailed" {
|
||||||
|
return fmt.Errorf("Async operation %q returned %q", status.Operation, status.Status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var info api.Item
|
||||||
|
err = api.DecodeJSON(resp, &info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.setMetaData(&info)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Async operation didn't complete after %v", fs.Config.Timeout)
|
||||||
|
}
|
||||||
|
|
||||||
// Copy src to this remote using server side copy operations.
|
// Copy src to this remote using server side copy operations.
|
||||||
//
|
//
|
||||||
// This is stored with the remote path given
|
// This is stored with the remote path given
|
||||||
@ -535,19 +590,59 @@ func (f *Fs) Precision() time.Duration {
|
|||||||
// Will only be called if src.Fs().Name() == f.Name()
|
// Will only be called if src.Fs().Name() == f.Name()
|
||||||
//
|
//
|
||||||
// If it isn't possible then return fs.ErrorCantCopy
|
// If it isn't possible then return fs.ErrorCantCopy
|
||||||
//func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
|
func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
|
||||||
// srcObj, ok := src.(*Object)
|
srcObj, ok := src.(*Object)
|
||||||
// if !ok {
|
if !ok {
|
||||||
// fs.Debug(src, "Can't copy - not same remote type")
|
fs.Debug(src, "Can't copy - not same remote type")
|
||||||
// return nil, fs.ErrorCantCopy
|
return nil, fs.ErrorCantCopy
|
||||||
// }
|
}
|
||||||
// srcFs := srcObj.acd
|
err := srcObj.readMetaData()
|
||||||
// _, err := f.c.ObjectCopy(srcFs.container, srcFs.root+srcObj.remote, f.container, f.root+remote, nil)
|
if err != nil {
|
||||||
// if err != nil {
|
return nil, err
|
||||||
// return nil, err
|
}
|
||||||
// }
|
|
||||||
// return f.NewFsObject(remote), nil
|
// Create temporary object
|
||||||
//}
|
dstObj, leaf, directoryID, err := f.createObject(remote, srcObj.modTime, srcObj.size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the object
|
||||||
|
opts := api.Opts{
|
||||||
|
Method: "POST",
|
||||||
|
Path: "/drive/items/" + srcObj.id + "/action.copy",
|
||||||
|
ExtraHeaders: map[string]string{"Prefer": "respond-async"},
|
||||||
|
NoResponse: true,
|
||||||
|
}
|
||||||
|
replacedLeaf := replaceReservedChars(leaf)
|
||||||
|
copy := api.CopyItemRequest{
|
||||||
|
Name: &replacedLeaf,
|
||||||
|
ParentReference: api.ItemReference{
|
||||||
|
ID: directoryID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var resp *http.Response
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err = f.srv.CallJSON(&opts, ©, nil)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// read location header
|
||||||
|
location := resp.Header.Get("Location")
|
||||||
|
if location == "" {
|
||||||
|
return nil, fmt.Errorf("Didn't receive location header in copy response")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for job to finish
|
||||||
|
err = f.waitForJob(location, dstObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dstObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Purge deletes all the files and the container
|
// Purge deletes all the files and the container
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user