From d51501938a355f95dd79995f52a59fc1509f1049 Mon Sep 17 00:00:00 2001 From: albertony <12441419+albertony@users.noreply.github.com> Date: Tue, 4 Sep 2018 21:02:35 +0200 Subject: [PATCH] jottacloud: add link sharing support --- backend/jottacloud/api/types.go | 21 +++++---- backend/jottacloud/jottacloud.go | 80 ++++++++++++++++++++++++++------ cmd/link/link.go | 4 +- docs/content/jottacloud.md | 5 ++ docs/content/overview.md | 2 +- 5 files changed, 87 insertions(+), 25 deletions(-) diff --git a/backend/jottacloud/api/types.go b/backend/jottacloud/api/types.go index 22d7d711e..23ae8277b 100644 --- a/backend/jottacloud/api/types.go +++ b/backend/jottacloud/api/types.go @@ -233,16 +233,17 @@ GET http://www.jottacloud.com/JFS////.../ // JottaFile represents a Jottacloud file type JottaFile struct { - XMLName xml.Name - Name string `xml:"name,attr"` - Deleted Flag `xml:"deleted,attr"` - State string `xml:"currentRevision>state"` - CreatedAt Time `xml:"currentRevision>created"` - ModifiedAt Time `xml:"currentRevision>modified"` - Updated Time `xml:"currentRevision>updated"` - Size int64 `xml:"currentRevision>size"` - MimeType string `xml:"currentRevision>mime"` - MD5 string `xml:"currentRevision>md5"` + XMLName xml.Name + Name string `xml:"name,attr"` + Deleted Flag `xml:"deleted,attr"` + PublicSharePath string `xml:"publicSharePath"` + State string `xml:"currentRevision>state"` + CreatedAt Time `xml:"currentRevision>created"` + ModifiedAt Time `xml:"currentRevision>modified"` + Updated Time `xml:"currentRevision>updated"` + Size int64 `xml:"currentRevision>size"` + MimeType string `xml:"currentRevision>mime"` + MD5 string `xml:"currentRevision>md5"` } // Error is a custom Error for wrapping Jottacloud error responses diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go index e2f82a32f..da2c1b25c 100644 --- a/backend/jottacloud/jottacloud.go +++ b/backend/jottacloud/jottacloud.go @@ -40,6 +40,7 @@ const ( defaultMountpoint = "Sync" rootURL = "https://www.jottacloud.com/jfs/" apiURL = "https://api.jottacloud.com" + shareURL = "https://www.jottacloud.com/" cachePrefix = "rclone-jcmd5-" ) @@ -77,6 +78,11 @@ func init() { Help: "Delete files permanently rather than putting them into the trash.", Default: false, Advanced: true, + }, { + Name: "unlink", + Help: "Remove existing public link to file/folder with link command rather than creating.", + Default: false, + Advanced: true, }}, }) } @@ -88,6 +94,7 @@ type Options struct { Mountpoint string `config:"mountpoint"` MD5MemoryThreshold fs.SizeSuffix `config:"md5_memory_limit"` HardDelete bool `config:"hard_delete"` + Unlink bool `config:"unlink"` } // Fs represents a remote jottacloud @@ -614,7 +621,7 @@ func (f *Fs) purgeCheck(dir string, check bool) (err error) { return shouldRetry(resp, err) }) if err != nil { - return errors.Wrap(err, "rmdir failed") + return errors.Wrap(err, "couldn't purge directory") } // TODO: Parse response? @@ -687,7 +694,7 @@ func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) { info, err := f.copyOrMove("cp", srcObj.filePath(), remote) if err != nil { - return nil, errors.Wrap(err, "copy failed") + return nil, errors.Wrap(err, "couldn't copy file") } return f.newObjectWithInfo(remote, info) @@ -717,7 +724,7 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) { info, err := f.copyOrMove("mv", srcObj.filePath(), remote) if err != nil { - return nil, errors.Wrap(err, "move failed") + return nil, errors.Wrap(err, "couldn't move file") } return f.newObjectWithInfo(remote, info) @@ -761,11 +768,57 @@ func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error { _, err = f.copyOrMove("mvDir", path.Join(f.endpointURL, replaceReservedChars(srcPath))+"/", dstRemote) if err != nil { - return errors.Wrap(err, "moveDir failed") + return errors.Wrap(err, "couldn't move directory") } return nil } +// PublicLink generates a public link to the remote path (usually readable by anyone) +func (f *Fs) PublicLink(remote string) (link string, err error) { + opts := rest.Opts{ + Method: "GET", + Path: f.filePath(remote), + Parameters: url.Values{}, + } + + if f.opt.Unlink { + opts.Parameters.Set("mode", "disableShare") + } else { + opts.Parameters.Set("mode", "enableShare") + } + + var resp *http.Response + var result api.JottaFile + err = f.pacer.Call(func() (bool, error) { + resp, err = f.srv.CallXML(&opts, nil, &result) + return shouldRetry(resp, err) + }) + + if apiErr, ok := err.(*api.Error); ok { + // does not exist + if apiErr.StatusCode == http.StatusNotFound { + return "", fs.ErrorObjectNotFound + } + } + if err != nil { + if f.opt.Unlink { + return "", errors.Wrap(err, "couldn't remove public link") + } + return "", errors.Wrap(err, "couldn't create public link") + } + if f.opt.Unlink { + if result.PublicSharePath != "" { + return "", errors.Errorf("couldn't remove public link - %q", result.PublicSharePath) + } + return "", nil + } + if result.PublicSharePath == "" { + return "", errors.New("couldn't create public link - no link path received") + } + link = path.Join(shareURL, result.PublicSharePath) + return link, nil +} + // About gets quota information func (f *Fs) About() (*fs.Usage, error) { info, err := f.getAccountInfo() @@ -1039,13 +1092,14 @@ func (o *Object) Remove() error { // Check the interfaces are satisfied var ( - _ fs.Fs = (*Fs)(nil) - _ fs.Purger = (*Fs)(nil) - _ fs.Copier = (*Fs)(nil) - _ fs.Mover = (*Fs)(nil) - _ fs.DirMover = (*Fs)(nil) - _ fs.ListRer = (*Fs)(nil) - _ fs.Abouter = (*Fs)(nil) - _ fs.Object = (*Object)(nil) - _ fs.MimeTyper = (*Object)(nil) + _ fs.Fs = (*Fs)(nil) + _ fs.Purger = (*Fs)(nil) + _ fs.Copier = (*Fs)(nil) + _ fs.Mover = (*Fs)(nil) + _ fs.DirMover = (*Fs)(nil) + _ fs.ListRer = (*Fs)(nil) + _ fs.PublicLinker = (*Fs)(nil) + _ fs.Abouter = (*Fs)(nil) + _ fs.Object = (*Object)(nil) + _ fs.MimeTyper = (*Object)(nil) ) diff --git a/cmd/link/link.go b/cmd/link/link.go index c725f8eae..4c3fba85d 100644 --- a/cmd/link/link.go +++ b/cmd/link/link.go @@ -34,7 +34,9 @@ without account. if err != nil { return err } - fmt.Println(link) + if link != "" { + fmt.Println(link) + } return nil }) }, diff --git a/docs/content/jottacloud.md b/docs/content/jottacloud.md index 566433406..c6ee157f3 100644 --- a/docs/content/jottacloud.md +++ b/docs/content/jottacloud.md @@ -149,6 +149,11 @@ Controls whether files are sent to the trash or deleted permanently. Defaults to false, namely sending files to the trash. Use `--jottacloud-hard-delete=true` to delete files permanently instead. +#### --jottacloud-unlink #### + +Set to true to make the link command remove existing public link to file/folder. +Default is false, meaning link command will create or retrieve public link. + ### Troubleshooting ### Jottacloud exhibits some inconsistent behaviours regarding deleted files and folders which may cause Copy, Move and DirMove operations to previously deleted paths to fail. Emptying the trash should help in such cases. \ No newline at end of file diff --git a/docs/content/overview.md b/docs/content/overview.md index a7092e7c5..e568d16b1 100644 --- a/docs/content/overview.md +++ b/docs/content/overview.md @@ -138,7 +138,7 @@ operations more efficient. | Google Drive | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | HTTP | No | No | No | No | No | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | Hubic | Yes † | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | Yes | -| Jottacloud | Yes | Yes | Yes | Yes | No | Yes | No | No | No | +| Jottacloud | Yes | Yes | Yes | Yes | No | Yes | No | Yes | No | | Mega | Yes | No | Yes | Yes | No | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | Yes | | Microsoft Azure Blob Storage | Yes | Yes | No | No | No | Yes | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | Microsoft OneDrive | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | Yes |