From c4b8df69036117cdb8415873d0d99635390b6c13 Mon Sep 17 00:00:00 2001 From: buengese Date: Mon, 22 Feb 2021 00:37:38 +0100 Subject: [PATCH] fichier: implement copy & move --- backend/fichier/api.go | 85 ++++++++++++++++++++++++++++++++++++++ backend/fichier/fichier.go | 71 ++++++++++++++++++++++++++++++- backend/fichier/object.go | 4 ++ backend/fichier/structs.go | 39 ++++++++++++++++- docs/content/overview.md | 2 +- 5 files changed, 198 insertions(+), 3 deletions(-) diff --git a/backend/fichier/api.go b/backend/fichier/api.go index 483f8c62c..8a9aa0754 100644 --- a/backend/fichier/api.go +++ b/backend/fichier/api.go @@ -48,6 +48,41 @@ func shouldRetry(resp *http.Response, err error) (bool, error) { 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) { request := DownloadRequest{ URL: url, @@ -308,6 +343,56 @@ func (f *Fs) deleteFile(ctx context.Context, url string) (response *GenericOKRes 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) { // fs.Debugf(f, "Requesting Upload node") diff --git a/backend/fichier/fichier.go b/backend/fichier/fichier.go index fc7490b26..7b722b34a 100644 --- a/backend/fichier/fichier.go +++ b/backend/fichier/fichier.go @@ -363,7 +363,6 @@ func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size fs: f, remote: remote, file: File{ - ACL: 0, CDN: 0, Checksum: link.Whirlpool, ContentType: "", @@ -416,9 +415,79 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { 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 var ( _ fs.Fs = (*Fs)(nil) + _ fs.Mover = (*Fs)(nil) + _ fs.Copier = (*Fs)(nil) _ fs.PutUncheckeder = (*Fs)(nil) _ dircache.DirCacher = (*Fs)(nil) ) diff --git a/backend/fichier/object.go b/backend/fichier/object.go index 95c4fe783..09ad7b29c 100644 --- a/backend/fichier/object.go +++ b/backend/fichier/object.go @@ -72,6 +72,10 @@ func (o *Object) SetModTime(context.Context, time.Time) error { //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 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) { fs.FixRangeOption(options, o.file.Size) diff --git a/backend/fichier/structs.go b/backend/fichier/structs.go index 796c7568d..c20e459f1 100644 --- a/backend/fichier/structs.go +++ b/backend/fichier/structs.go @@ -1,5 +1,10 @@ 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 type ListFolderRequest struct { FolderID int `json:"folder_id"` @@ -49,6 +54,39 @@ type MakeFolderResponse struct { 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 type GetUploadNodeResponse struct { ID string `json:"id"` @@ -86,7 +124,6 @@ type EndFileUploadResponse struct { // File is the structure how 1Fichier returns a File type File struct { - ACL int `json:"acl"` CDN int `json:"cdn"` Checksum string `json:"checksum"` ContentType string `json:"content-type"` diff --git a/docs/content/overview.md b/docs/content/overview.md index 78b53e790..23da7e105 100644 --- a/docs/content/overview.md +++ b/docs/content/overview.md @@ -330,7 +330,7 @@ upon backend specific capabilities. | 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 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 |