forked from TrueCloudLab/rclone
fichier: implement copy & move
This commit is contained in:
parent
0dd3ae5e0d
commit
c4b8df6903
5 changed files with 198 additions and 3 deletions
|
@ -48,6 +48,41 @@ func shouldRetry(resp *http.Response, err error) (bool, error) {
|
||||||
|
|
||||||
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
|
var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
|
||||||
|
|
||||||
|
func (f *Fs) createObject(ctx context.Context, remote string) (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(ctx, remote, true)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Temporary Object under construction
|
||||||
|
o = &Object{
|
||||||
|
fs: f,
|
||||||
|
remote: remote,
|
||||||
|
}
|
||||||
|
return o, leaf, directoryID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fs) readFileInfo(ctx context.Context, url string) (*File, error) {
|
||||||
|
request := FileInfoRequest{
|
||||||
|
URL: url,
|
||||||
|
}
|
||||||
|
opts := rest.Opts{
|
||||||
|
Method: "POST",
|
||||||
|
Path: "/file/info.cgi",
|
||||||
|
}
|
||||||
|
|
||||||
|
var file File
|
||||||
|
err := f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err := f.rest.CallJSON(ctx, &opts, &request, &file)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't read file info")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &file, err
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fs) getDownloadToken(ctx context.Context, url string) (*GetTokenResponse, error) {
|
func (f *Fs) getDownloadToken(ctx context.Context, url string) (*GetTokenResponse, error) {
|
||||||
request := DownloadRequest{
|
request := DownloadRequest{
|
||||||
URL: url,
|
URL: url,
|
||||||
|
@ -308,6 +343,56 @@ func (f *Fs) deleteFile(ctx context.Context, url string) (response *GenericOKRes
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fs) moveFile(ctx context.Context, url string, folderID int, rename string) (response *MoveFileResponse, err error) {
|
||||||
|
request := &MoveFileRequest{
|
||||||
|
URLs: []string{url},
|
||||||
|
FolderID: folderID,
|
||||||
|
Rename: rename,
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := rest.Opts{
|
||||||
|
Method: "POST",
|
||||||
|
Path: "/file/mv.cgi",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = &MoveFileResponse{}
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err := f.rest.CallJSON(ctx, &opts, request, response)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't copy file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fs) copyFile(ctx context.Context, url string, folderID int, rename string) (response *CopyFileResponse, err error) {
|
||||||
|
request := &CopyFileRequest{
|
||||||
|
URLs: []string{url},
|
||||||
|
FolderID: folderID,
|
||||||
|
Rename: rename,
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := rest.Opts{
|
||||||
|
Method: "POST",
|
||||||
|
Path: "/file/cp.cgi",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = &CopyFileResponse{}
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err := f.rest.CallJSON(ctx, &opts, request, response)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't copy file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fs) getUploadNode(ctx context.Context) (response *GetUploadNodeResponse, err error) {
|
func (f *Fs) getUploadNode(ctx context.Context) (response *GetUploadNodeResponse, err error) {
|
||||||
// fs.Debugf(f, "Requesting Upload node")
|
// fs.Debugf(f, "Requesting Upload node")
|
||||||
|
|
||||||
|
|
|
@ -363,7 +363,6 @@ func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size
|
||||||
fs: f,
|
fs: f,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
file: File{
|
file: File{
|
||||||
ACL: 0,
|
|
||||||
CDN: 0,
|
CDN: 0,
|
||||||
Checksum: link.Whirlpool,
|
Checksum: link.Whirlpool,
|
||||||
ContentType: "",
|
ContentType: "",
|
||||||
|
@ -416,9 +415,79 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move src to this remote using server side move operations.
|
||||||
|
func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
|
||||||
|
srcObj, ok := src.(*Object)
|
||||||
|
if !ok {
|
||||||
|
fs.Debugf(src, "Can't move - not same remote type")
|
||||||
|
return nil, fs.ErrorCantMove
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create temporary object
|
||||||
|
dstObj, leaf, directoryID, err := f.createObject(ctx, remote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
folderID, err := strconv.Atoi(directoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := f.moveFile(ctx, srcObj.file.URL, folderID, leaf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't move file")
|
||||||
|
}
|
||||||
|
if resp.Status != "OK" {
|
||||||
|
return nil, errors.New("couldn't move file")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := f.readFileInfo(ctx, resp.URLs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("couldn't read file data")
|
||||||
|
}
|
||||||
|
dstObj.setMetaData(*file)
|
||||||
|
return dstObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy src to this remote using server side move operations.
|
||||||
|
func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
|
||||||
|
srcObj, ok := src.(*Object)
|
||||||
|
if !ok {
|
||||||
|
fs.Debugf(src, "Can't move - not same remote type")
|
||||||
|
return nil, fs.ErrorCantMove
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create temporary object
|
||||||
|
dstObj, leaf, directoryID, err := f.createObject(ctx, remote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
folderID, err := strconv.Atoi(directoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := f.copyFile(ctx, srcObj.file.URL, folderID, leaf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't move file")
|
||||||
|
}
|
||||||
|
if resp.Status != "OK" {
|
||||||
|
return nil, errors.New("couldn't move file")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := f.readFileInfo(ctx, resp.URLs[0].ToURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("couldn't read file data")
|
||||||
|
}
|
||||||
|
dstObj.setMetaData(*file)
|
||||||
|
return dstObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check the interfaces are satisfied
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.Fs = (*Fs)(nil)
|
_ fs.Fs = (*Fs)(nil)
|
||||||
|
_ fs.Mover = (*Fs)(nil)
|
||||||
|
_ fs.Copier = (*Fs)(nil)
|
||||||
_ fs.PutUncheckeder = (*Fs)(nil)
|
_ fs.PutUncheckeder = (*Fs)(nil)
|
||||||
_ dircache.DirCacher = (*Fs)(nil)
|
_ dircache.DirCacher = (*Fs)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -72,6 +72,10 @@ func (o *Object) SetModTime(context.Context, time.Time) error {
|
||||||
//return errors.New("setting modtime is not supported for 1fichier remotes")
|
//return errors.New("setting modtime is not supported for 1fichier remotes")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Object) setMetaData(file File) {
|
||||||
|
o.file = file
|
||||||
|
}
|
||||||
|
|
||||||
// Open opens the file for read. Call Close() on the returned io.ReadCloser
|
// Open opens the file for read. Call Close() on the returned io.ReadCloser
|
||||||
func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
|
func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
|
||||||
fs.FixRangeOption(options, o.file.Size)
|
fs.FixRangeOption(options, o.file.Size)
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package fichier
|
package fichier
|
||||||
|
|
||||||
|
// FileInfoRequest is the request structure of the corresponding request
|
||||||
|
type FileInfoRequest struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListFolderRequest is the request structure of the corresponding request
|
// ListFolderRequest is the request structure of the corresponding request
|
||||||
type ListFolderRequest struct {
|
type ListFolderRequest struct {
|
||||||
FolderID int `json:"folder_id"`
|
FolderID int `json:"folder_id"`
|
||||||
|
@ -49,6 +54,39 @@ type MakeFolderResponse struct {
|
||||||
FolderID int `json:"folder_id"`
|
FolderID int `json:"folder_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MoveFileRequest is the request structure of the corresponding request
|
||||||
|
type MoveFileRequest struct {
|
||||||
|
URLs []string `json:"urls"`
|
||||||
|
FolderID int `json:"destination_folder_id"`
|
||||||
|
Rename string `json:"rename,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveFileResponse is the response structure of the corresponding request
|
||||||
|
type MoveFileResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
URLs []string `json:"urls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFileRequest is the request structure of the corresponding request
|
||||||
|
type CopyFileRequest struct {
|
||||||
|
URLs []string `json:"urls"`
|
||||||
|
FolderID int `json:"folder_id"`
|
||||||
|
Rename string `json:"rename,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFileResponse is the response structure of the corresponding request
|
||||||
|
type CopyFileResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Copied int `json:"copied"`
|
||||||
|
URLs []FileCopy `json:"urls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCopy is used in the the CopyFileResponse
|
||||||
|
type FileCopy struct {
|
||||||
|
FromURL string `json:"from_url"`
|
||||||
|
ToURL string `json:"to_url"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetUploadNodeResponse is the response structure of the corresponding request
|
// GetUploadNodeResponse is the response structure of the corresponding request
|
||||||
type GetUploadNodeResponse struct {
|
type GetUploadNodeResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -86,7 +124,6 @@ type EndFileUploadResponse struct {
|
||||||
|
|
||||||
// File is the structure how 1Fichier returns a File
|
// File is the structure how 1Fichier returns a File
|
||||||
type File struct {
|
type File struct {
|
||||||
ACL int `json:"acl"`
|
|
||||||
CDN int `json:"cdn"`
|
CDN int `json:"cdn"`
|
||||||
Checksum string `json:"checksum"`
|
Checksum string `json:"checksum"`
|
||||||
ContentType string `json:"content-type"`
|
ContentType string `json:"content-type"`
|
||||||
|
|
|
@ -330,7 +330,7 @@ upon backend specific capabilities.
|
||||||
|
|
||||||
| Name | Purge | Copy | Move | DirMove | CleanUp | ListR | StreamUpload | LinkSharing | About | EmptyDir |
|
| Name | Purge | Copy | Move | DirMove | CleanUp | ListR | StreamUpload | LinkSharing | About | EmptyDir |
|
||||||
| ---------------------------- |:-----:|:----:|:----:|:-------:|:-------:|:-----:|:------------:|:------------:|:-----:| :------: |
|
| ---------------------------- |:-----:|:----:|:----:|:-------:|:-------:|:-----:|:------------:|:------------:|:-----:| :------: |
|
||||||
| 1Fichier | No | No | No | No | No | No | No | No | No | Yes |
|
| 1Fichier | No | Yes | Yes | No | No | No | No | No | No | Yes |
|
||||||
| Amazon Drive | Yes | No | Yes | Yes | No [#575](https://github.com/rclone/rclone/issues/575) | No | No | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | Yes |
|
| Amazon Drive | Yes | No | Yes | Yes | No [#575](https://github.com/rclone/rclone/issues/575) | No | No | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | Yes |
|
||||||
| Amazon S3 | No | Yes | No | No | Yes | Yes | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | No |
|
| Amazon S3 | No | Yes | No | No | Yes | Yes | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | No |
|
||||||
| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | Yes | No | No |
|
| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | Yes | No | No |
|
||||||
|
|
Loading…
Add table
Reference in a new issue