diff --git a/backend/b2/api/types.go b/backend/b2/api/types.go index aff8404ee..75cd476f3 100644 --- a/backend/b2/api/types.go +++ b/backend/b2/api/types.go @@ -311,3 +311,13 @@ type CancelLargeFileResponse struct { AccountID string `json:"accountId"` // The identifier for the account. BucketID string `json:"bucketId"` // The unique ID of the bucket. } + +// CopyFileRequest is as passed to b2_copy_file +type CopyFileRequest struct { + SourceID string `json:"sourceFileId"` // The ID of the source file being copied. + Name string `json:"fileName"` // The name of the new file being created. + Range string `json:"range,omitempty"` // The range of bytes to copy. If not provided, the whole source file will be copied. + MetadataDirective string `json:"metadataDirective,omitempty"` // The strategy for how to populate metadata for the new file: COPY or REPLACE + ContentType string `json:"contentType,omitempty"` // The MIME type of the content of the file (REPLACE only) + Info map[string]string `json:"fileInfo,omitempty"` // This field stores the metadata that will be stored with the file. (REPLACE only) +} diff --git a/backend/b2/b2.go b/backend/b2/b2.go index ec81be207..7a26c10a1 100644 --- a/backend/b2/b2.go +++ b/backend/b2/b2.go @@ -1066,6 +1066,58 @@ func (f *Fs) CleanUp() error { return f.purge(true) } +// Copy src to this remote using server side copy operations. +// +// This is stored with the remote path given +// +// It returns the destination Object and a possible error +// +// Will only be called if src.Fs().Name() == f.Name() +// +// If it isn't possible then return fs.ErrorCantCopy +func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) { + err := f.Mkdir("") + if err != nil { + return nil, err + } + srcObj, ok := src.(*Object) + if !ok { + fs.Debugf(src, "Can't copy - not same remote type") + return nil, fs.ErrorCantCopy + } + srcFs := srcObj.fs + if srcFs.bucket != f.bucket { + fs.Debugf(src, "Can't copy - not same bucket") + return nil, fs.ErrorCantCopy + } + opts := rest.Opts{ + Method: "POST", + Path: "/b2_copy_file", + } + var request = api.CopyFileRequest{ + SourceID: srcObj.id, + Name: f.root + remote, + MetadataDirective: "COPY", + } + var response api.FileInfo + err = f.pacer.Call(func() (bool, error) { + resp, err := f.srv.CallJSON(&opts, &request, &response) + return f.shouldRetry(resp, err) + }) + if err != nil { + return nil, err + } + o := &Object{ + fs: f, + remote: remote, + } + err = o.decodeMetaDataFileInfo(&response) + if err != nil { + return nil, err + } + return o, nil +} + // Hashes returns the supported hash sets. func (f *Fs) Hashes() hash.Set { return hash.Set(hash.SHA1) @@ -1567,6 +1619,7 @@ func (o *Object) ID() string { var ( _ fs.Fs = &Fs{} _ fs.Purger = &Fs{} + _ fs.Copier = &Fs{} _ fs.PutStreamer = &Fs{} _ fs.CleanUpper = &Fs{} _ fs.ListRer = &Fs{} diff --git a/docs/content/overview.md b/docs/content/overview.md index b65bf4f45..803a97f76 100644 --- a/docs/content/overview.md +++ b/docs/content/overview.md @@ -133,7 +133,7 @@ operations more efficient. | ---------------------------- |:-----:|:----:|:----:|:-------:|:-------:|:-----:|:------------:|:------------:|:-----:| | Amazon Drive | Yes | No | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | Amazon S3 | No | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | -| Backblaze B2 | No | No | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | +| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | Box | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | No | | Dropbox | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | Yes | | FTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |