From fc4fe337035fe5c1a221e2ca3255c8294db9e2ce Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 11:58:16 +0100
Subject: [PATCH 001/560] Add Nick to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 8a88a96bfee5a..ae346ac956637 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -602,3 +602,4 @@ put them back in again.` >}}
* Rob Pickerill
* Andrey
* Eric Wolf <19wolf@gmail.com>
+ * Nick
From a449dd7d1cf9902e0bd5c3f502364a9ac1157790 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 11:58:16 +0100
Subject: [PATCH 002/560] Add Jason Zheng to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index ae346ac956637..25fb30f5ae5a2 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -603,3 +603,4 @@ put them back in again.` >}}
* Andrey
* Eric Wolf <19wolf@gmail.com>
* Nick
+ * Jason Zheng
From 9eb3470c9c6e8ce3387bb845cb637713dbf2c6a5 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 11:58:16 +0100
Subject: [PATCH 003/560] Add Matthew Vernon to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 25fb30f5ae5a2..23f53103628a0 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -604,3 +604,4 @@ put them back in again.` >}}
* Eric Wolf <19wolf@gmail.com>
* Nick
* Jason Zheng
+ * Matthew Vernon
From 26db80c2709351478ce570455b22f64b6161519a Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 11:28:33 +0100
Subject: [PATCH 004/560] ftp: revert to upstream github.com/jlaffaye/ftp from
our fork
...now all of our patches have been merged #5810
---
backend/ftp/ftp.go | 2 +-
go.mod | 2 +-
go.sum | 9 ++++++---
3 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/backend/ftp/ftp.go b/backend/ftp/ftp.go
index c5b39b24c5771..92b84fe9c014c 100644
--- a/backend/ftp/ftp.go
+++ b/backend/ftp/ftp.go
@@ -15,7 +15,7 @@ import (
"sync"
"time"
- "github.com/rclone/ftp"
+ "github.com/jlaffaye/ftp"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/config"
diff --git a/go.mod b/go.mod
index 5051c7c64414a..f9364e3219928 100644
--- a/go.mod
+++ b/go.mod
@@ -44,7 +44,6 @@ require (
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.12.1
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8
- github.com/rclone/ftp v1.0.0-210902h
github.com/rfjakob/eme v1.1.2
github.com/shirou/gopsutil/v3 v3.22.3
github.com/sirupsen/logrus v1.8.1
@@ -76,6 +75,7 @@ require (
github.com/golang-jwt/jwt/v4 v4.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
+ github.com/jlaffaye/ftp v0.0.0-20220524001917-dfa1e758f3af
golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
diff --git a/go.sum b/go.sum
index 16b4000956b18..ff01c201275dc 100644
--- a/go.sum
+++ b/go.sum
@@ -320,6 +320,10 @@ github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek=
github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -346,8 +350,9 @@ github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aW
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
-github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf h1:2IYBd5TD/maMqTU2YUzp2tJL4cNaOYQ9EBullN9t9pk=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
+github.com/jlaffaye/ftp v0.0.0-20220524001917-dfa1e758f3af h1:sh8vAWJ+vr9izhkDAMS3JRGDIjj0tNVwxfwd+2U2xMo=
+github.com/jlaffaye/ftp v0.0.0-20220524001917-dfa1e758f3af/go.mod h1:oZaomI+9/et52UBjvNU9LCIqmgt816+7ljXCx0EIPzo=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -494,8 +499,6 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 h1:Y258uzXU/potCYnQd1r6wlAnoMB68BiCkCcCnKx1SH8=
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8/go.mod h1:bSJjRokAHHOhA+XFxplld8w2R/dXLH7Z3BZ532vhFwU=
-github.com/rclone/ftp v1.0.0-210902h h1:e9rbDiTdorXRsRtUOdbr6asesJkYZQ9efy1ts5OEBb8=
-github.com/rclone/ftp v1.0.0-210902h/go.mod h1:GtHgnfXJAx17bmdVU8kiItiUNFkMbFt+sIg0SwAfyx0=
github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4=
github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
From fb587371426addf6684ab14592c294df7d15da59 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 20 Apr 2022 17:56:15 +0100
Subject: [PATCH 005/560] fstests: check for wrapped errors in ListR test
---
fstest/fstests/fstests.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 9b3031acdefc3..11cf4dc8bb994 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -1558,12 +1558,12 @@ func Run(t *testing.T, opt *Opt) {
}
return nil
})
- if err != errFound && err != errTooMany {
+ if !errors.Is(err, errFound) && !errors.Is(err, errTooMany) {
assert.NoError(t, err)
}
- if err != errTooMany {
- assert.True(t, file1Found, "file1Root not found")
- assert.True(t, file2Found, "file2Root not found")
+ if !errors.Is(err, errTooMany) {
+ assert.True(t, file1Found, "file1Root %q not found", file1Root.Path)
+ assert.True(t, file2Found, "file2Root %q not found", file2Root.Path)
} else {
t.Logf("Too many files to list - giving up")
}
From e58d75e4d7aa6969c202a962a06635d83a52d1af Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 21 Apr 2022 09:58:59 +0100
Subject: [PATCH 006/560] drive: make backend config -o config add a combined
AllDrives remote
This adjusts
rclone backend drives -o config drive:
So that it also emits a config section called `AllDrives` which uses
the combine backend to make a backend which combines all the shared
drives into one.
It also makes sure that all the shared drive names are valid rclone
config names, deduplicating if necessary.
Fixes #4506
---
backend/drive/drive.go | 34 ++++++++++++++++++++++++++++------
1 file changed, 28 insertions(+), 6 deletions(-)
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index dc26a39d70790..9aeeec5a04153 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -18,6 +18,7 @@ import (
"mime"
"net/http"
"path"
+ "regexp"
"sort"
"strconv"
"strings"
@@ -3241,7 +3242,7 @@ This will return a JSON list of objects like this
With the -o config parameter it will output the list in a format
suitable for adding to a config file to make aliases for all the
-drives found.
+drives found and a combined drive.
[My Drive]
type = alias
@@ -3251,10 +3252,15 @@ drives found.
type = alias
remote = drive,team_drive=0ABCDEFabcdefghijkl,root_folder_id=:
-Adding this to the rclone config file will cause those team drives to
-be accessible with the aliases shown. This may require manual editing
-of the names.
+ [AllDrives]
+ type = combine
+ remote = "My Drive=My Drive:" "Test Drive=Test Drive:"
+Adding this to the rclone config file will cause those team drives to
+be accessible with the aliases shown. Any illegal charactes will be
+substituted with "_" and duplicate names will have numbers suffixed.
+It will also add a remote called AllDrives which shows all the shared
+drives combined into one directory tree.
`,
}, {
Name: "untrash",
@@ -3370,14 +3376,30 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
if err != nil {
return nil, err
}
+ re := regexp.MustCompile(`[^\w_. -]+`)
if _, ok := opt["config"]; ok {
lines := []string{}
- for _, drive := range drives {
+ upstreams := []string{}
+ names := make(map[string]struct{}, len(drives))
+ for i, drive := range drives {
+ name := re.ReplaceAllString(drive.Name, "_")
+ for {
+ if _, found := names[name]; !found {
+ break
+ }
+ name += fmt.Sprintf("-%d", i)
+ }
+ names[name] = struct{}{}
lines = append(lines, "")
- lines = append(lines, fmt.Sprintf("[%s]", drive.Name))
+ lines = append(lines, fmt.Sprintf("[%s]", name))
lines = append(lines, fmt.Sprintf("type = alias"))
lines = append(lines, fmt.Sprintf("remote = %s,team_drive=%s,root_folder_id=:", f.name, drive.Id))
+ upstreams = append(upstreams, fmt.Sprintf(`"%s=%s:"`, name, name))
}
+ lines = append(lines, "")
+ lines = append(lines, fmt.Sprintf("[AllDrives]"))
+ lines = append(lines, fmt.Sprintf("type = combine"))
+ lines = append(lines, fmt.Sprintf("upstreams = %s", strings.Join(upstreams, " ")))
return lines, nil
}
return drives, nil
From 4b358ff43ba941e34b1c66134b756b4e8a484822 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 20 Apr 2022 17:57:43 +0100
Subject: [PATCH 007/560] combine: backend to combine multiple remotes in one
directory tree
Fixes #5600
---
README.md | 13 +
backend/all/all.go | 1 +
backend/combine/combine.go | 941 +++++++++++++++++++++++
backend/combine/combine_internal_test.go | 94 +++
backend/combine/combine_test.go | 79 ++
bin/make_manual.py | 1 +
docs/content/_index.md | 14 +
docs/content/combine.md | 156 ++++
docs/content/docs.md | 1 +
docs/layouts/chrome/navbar.html | 1 +
fstest/test_all/config.yaml | 3 +
11 files changed, 1304 insertions(+)
create mode 100644 backend/combine/combine.go
create mode 100644 backend/combine/combine_internal_test.go
create mode 100644 backend/combine/combine_test.go
create mode 100644 docs/content/combine.md
diff --git a/README.md b/README.md
index 0235323b5ada6..206517733e77a 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,19 @@ Rclone *("rsync for cloud storage")* is a command-line program to sync files and
Please see [the full list of all storage providers and their features](https://rclone.org/overview/)
+### Virtual storage providers
+
+These backends adapt or modify other storage providers
+
+ * Alias: rename existing remotes [:page_facing_up:](https://rclone.org/alias/)
+ * Cache: cache remotes (DEPRECATED) [:page_facing_up:](https://rclone.org/cache/)
+ * Chunker: split large files [:page_facing_up:](https://rclone.org/chunker/)
+ * Combine: combine multiple remotes into a directory tree [:page_facing_up:](https://rclone.org/combine/)
+ * Compress: compress files [:page_facing_up:](https://rclone.org/compress/)
+ * Crypt: encrypt files [:page_facing_up:](https://rclone.org/crypt/)
+ * Hasher: hash files [:page_facing_up:](https://rclone.org/hasher/)
+ * Union: join multiple remotes to work together [:page_facing_up:](https://rclone.org/union/)
+
## Features
* MD5/SHA-1 hashes checked at all times for file integrity
diff --git a/backend/all/all.go b/backend/all/all.go
index a93909eca9053..44414b2879dd7 100644
--- a/backend/all/all.go
+++ b/backend/all/all.go
@@ -9,6 +9,7 @@ import (
_ "github.com/rclone/rclone/backend/box"
_ "github.com/rclone/rclone/backend/cache"
_ "github.com/rclone/rclone/backend/chunker"
+ _ "github.com/rclone/rclone/backend/combine"
_ "github.com/rclone/rclone/backend/compress"
_ "github.com/rclone/rclone/backend/crypt"
_ "github.com/rclone/rclone/backend/drive"
diff --git a/backend/combine/combine.go b/backend/combine/combine.go
new file mode 100644
index 0000000000000..c3c5ff38a792d
--- /dev/null
+++ b/backend/combine/combine.go
@@ -0,0 +1,941 @@
+// Package combine implents a backend to combine multipe remotes in a directory tree
+package combine
+
+/*
+ Have API to add/remove branches in the combine
+*/
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "path"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fs/cache"
+ "github.com/rclone/rclone/fs/config/configmap"
+ "github.com/rclone/rclone/fs/config/configstruct"
+ "github.com/rclone/rclone/fs/hash"
+ "github.com/rclone/rclone/fs/operations"
+ "github.com/rclone/rclone/fs/walk"
+ "golang.org/x/sync/errgroup"
+)
+
+// Register with Fs
+func init() {
+ fsi := &fs.RegInfo{
+ Name: "combine",
+ Description: "Combine several remotes into one",
+ NewFs: NewFs,
+ Options: []fs.Option{{
+ Name: "upstreams",
+ Help: `Upstreams for combining
+
+These should be in the form
+
+ dir=remote:path dir2=remote2:path
+
+Where before the = is specified the root directory and after is the remote to
+put there.
+
+Embedded spaces can be added using quotes
+
+ "dir=remote:path with space" "dir2=remote2:path with space"
+
+`,
+ Required: true,
+ Default: fs.SpaceSepList(nil),
+ }},
+ }
+ fs.Register(fsi)
+}
+
+// Options defines the configuration for this backend
+type Options struct {
+ Upstreams fs.SpaceSepList `config:"upstreams"`
+}
+
+// Fs represents a combine of upstreams
+type Fs struct {
+ name string // name of this remote
+ features *fs.Features // optional features
+ opt Options // options for this Fs
+ root string // the path we are working on
+ hashSet hash.Set // common hashes
+ when time.Time // directory times
+ upstreams map[string]*upstream // map of upstreams
+}
+
+// adjustment stores the info to add a prefix to a path or chop characters off
+type adjustment struct {
+ root string
+ rootSlash string
+ mountpoint string
+ mountpointSlash string
+}
+
+// newAdjustment makes a new path adjustment adjusting between mountpoint and root
+//
+// mountpoint is the point the upstream is mounted and root is the combine root
+func newAdjustment(root, mountpoint string) (a adjustment) {
+ return adjustment{
+ root: root,
+ rootSlash: root + "/",
+ mountpoint: mountpoint,
+ mountpointSlash: mountpoint + "/",
+ }
+}
+
+var errNotUnderRoot = errors.New("file not under root")
+
+// do makes the adjustment on s, mapping an upstream path into a combine path
+func (a *adjustment) do(s string) (string, error) {
+ absPath := join(a.mountpoint, s)
+ if a.root == "" {
+ return absPath, nil
+ }
+ if absPath == a.root {
+ return "", nil
+ }
+ if !strings.HasPrefix(absPath, a.rootSlash) {
+ return "", errNotUnderRoot
+ }
+ return absPath[len(a.rootSlash):], nil
+}
+
+// undo makes the adjustment on s, mapping a combine path into an upstream path
+func (a *adjustment) undo(s string) (string, error) {
+ absPath := join(a.root, s)
+ if absPath == a.mountpoint {
+ return "", nil
+ }
+ if !strings.HasPrefix(absPath, a.mountpointSlash) {
+ return "", errNotUnderRoot
+ }
+ return absPath[len(a.mountpointSlash):], nil
+}
+
+// upstream represents an upstream Fs
+type upstream struct {
+ f fs.Fs
+ parent *Fs
+ dir string // directory the upstream is mounted
+ pathAdjustment adjustment // how to fiddle with the path
+}
+
+// Create an upstream from the directory it is mounted on and the remote
+func (f *Fs) newUpstream(ctx context.Context, dir, remote string) (*upstream, error) {
+ uFs, err := cache.Get(ctx, remote)
+ if err == fs.ErrorIsFile {
+ return nil, fmt.Errorf("can't combine files yet, only directories %q: %w", remote, err)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("failed to create upstream %q: %w", remote, err)
+ }
+ u := &upstream{
+ f: uFs,
+ parent: f,
+ dir: dir,
+ pathAdjustment: newAdjustment(f.root, dir),
+ }
+ return u, nil
+}
+
+// NewFs constructs an Fs from the path.
+//
+// The returned Fs is the actual Fs, referenced by remote in the config
+func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (outFs fs.Fs, err error) {
+ // defer log.Trace(nil, "name=%q, root=%q, m=%v", name, root, m)("f=%+v, err=%v", &outFs, &err)
+ // Parse config into Options struct
+ opt := new(Options)
+ err = configstruct.Set(m, opt)
+ if err != nil {
+ return nil, err
+ }
+ // Backward compatible to old config
+ if len(opt.Upstreams) == 0 {
+ return nil, errors.New("combine can't point to an empty upstream - check the value of the upstreams setting")
+ }
+ for _, u := range opt.Upstreams {
+ if strings.HasPrefix(u, name+":") {
+ return nil, errors.New("can't point combine remote at itself - check the value of the upstreams setting")
+ }
+ }
+ isDir := false
+ for strings.HasSuffix(root, "/") {
+ root = root[:len(root)-1]
+ isDir = true
+ }
+
+ f := &Fs{
+ name: name,
+ root: root,
+ opt: *opt,
+ upstreams: make(map[string]*upstream, len(opt.Upstreams)),
+ when: time.Now(),
+ }
+
+ g, gCtx := errgroup.WithContext(ctx)
+ var mu sync.Mutex
+ for _, upstream := range opt.Upstreams {
+ upstream := upstream
+ g.Go(func() (err error) {
+ equal := strings.IndexRune(upstream, '=')
+ if equal < 0 {
+ return fmt.Errorf("no \"=\" in upstream definition %q", upstream)
+ }
+ dir, remote := upstream[:equal], upstream[equal+1:]
+ if dir == "" {
+ return fmt.Errorf("empty dir in upstream definition %q", upstream)
+ }
+ if remote == "" {
+ return fmt.Errorf("empty remote in upstream definition %q", upstream)
+ }
+ if strings.IndexRune(dir, '/') >= 0 {
+ return fmt.Errorf("dirs can't contain / (yet): %q", dir)
+ }
+ u, err := f.newUpstream(gCtx, dir, remote)
+ if err != nil {
+ return err
+ }
+ mu.Lock()
+ f.upstreams[dir] = u
+ mu.Unlock()
+ return nil
+ })
+ }
+ err = g.Wait()
+ if err != nil {
+ return nil, err
+ }
+ // check features
+ var features = (&fs.Features{
+ CaseInsensitive: true,
+ DuplicateFiles: false,
+ ReadMimeType: true,
+ WriteMimeType: true,
+ CanHaveEmptyDirectories: true,
+ BucketBased: true,
+ SetTier: true,
+ GetTier: true,
+ }).Fill(ctx, f)
+ canMove := true
+ for _, u := range f.upstreams {
+ features = features.Mask(ctx, u.f) // Mask all upstream fs
+ if !operations.CanServerSideMove(u.f) {
+ canMove = false
+ }
+ }
+ // We can move if all remotes support Move or Copy
+ if canMove {
+ features.Move = f.Move
+ }
+
+ // Enable ListR when upstreams either support ListR or is local
+ // But not when all upstreams are local
+ if features.ListR == nil {
+ for _, u := range f.upstreams {
+ if u.f.Features().ListR != nil {
+ features.ListR = f.ListR
+ } else if !u.f.Features().IsLocal {
+ features.ListR = nil
+ break
+ }
+ }
+ }
+
+ // Enable Purge when any upstreams support it
+ if features.Purge == nil {
+ for _, u := range f.upstreams {
+ if u.f.Features().Purge != nil {
+ features.Purge = f.Purge
+ break
+ }
+ }
+ }
+
+ // Enable Shutdown when any upstreams support it
+ if features.Shutdown == nil {
+ for _, u := range f.upstreams {
+ if u.f.Features().Shutdown != nil {
+ features.Shutdown = f.Shutdown
+ break
+ }
+ }
+ }
+
+ // Enable DirCacheFlush when any upstreams support it
+ if features.DirCacheFlush == nil {
+ for _, u := range f.upstreams {
+ if u.f.Features().DirCacheFlush != nil {
+ features.DirCacheFlush = f.DirCacheFlush
+ break
+ }
+ }
+ }
+
+ // Enable ChangeNotify when any upstreams support it
+ if features.ChangeNotify == nil {
+ for _, u := range f.upstreams {
+ if u.f.Features().ChangeNotify != nil {
+ features.ChangeNotify = f.ChangeNotify
+ break
+ }
+ }
+ }
+
+ f.features = features
+
+ // Get common intersection of hashes
+ var hashSet hash.Set
+ var first = true
+ for _, u := range f.upstreams {
+ if first {
+ hashSet = u.f.Hashes()
+ first = false
+ } else {
+ hashSet = hashSet.Overlap(u.f.Hashes())
+ }
+ }
+ f.hashSet = hashSet
+
+ // Check to see if the root is actually a file
+ if f.root != "" && !isDir {
+ _, err := f.NewObject(ctx, "")
+ if err != nil {
+ if err == fs.ErrorObjectNotFound || err == fs.ErrorNotAFile || err == fs.ErrorIsDir {
+ // File doesn't exist or is a directory so return old f
+ return f, nil
+ }
+ return nil, err
+ }
+
+ // Check to see if the root path is actually an existing file
+ f.root = path.Dir(f.root)
+ if f.root == "." {
+ f.root = ""
+ }
+ // Adjust path adjustment to remove leaf
+ for _, u := range f.upstreams {
+ u.pathAdjustment = newAdjustment(f.root, u.dir)
+ }
+ return f, fs.ErrorIsFile
+ }
+ return f, nil
+}
+
+// Run a function over all the upstreams in parallel
+func (f *Fs) multithread(ctx context.Context, fn func(context.Context, *upstream) error) error {
+ g, gCtx := errgroup.WithContext(ctx)
+ for _, u := range f.upstreams {
+ u := u
+ g.Go(func() (err error) {
+ return fn(gCtx, u)
+ })
+ }
+ return g.Wait()
+}
+
+// join the elements together but unline path.Join return empty string
+func join(elem ...string) string {
+ result := path.Join(elem...)
+ if result == "." {
+ return ""
+ }
+ if len(result) > 0 && result[0] == '/' {
+ result = result[1:]
+ }
+ return result
+}
+
+// find the upstream for the remote passed in, returning the upstream and the adjusted path
+func (f *Fs) findUpstream(remote string) (u *upstream, uRemote string, err error) {
+ // defer log.Trace(remote, "")("f=%v, uRemote=%q, err=%v", &u, &uRemote, &err)
+ for _, u := range f.upstreams {
+ uRemote, err = u.pathAdjustment.undo(remote)
+ if err == nil {
+ return u, uRemote, nil
+ }
+ }
+ return nil, "", fmt.Errorf("combine for remote %q: %w", remote, fs.ErrorDirNotFound)
+}
+
+// Name of the remote (as passed into NewFs)
+func (f *Fs) Name() string {
+ return f.name
+}
+
+// Root of the remote (as passed into NewFs)
+func (f *Fs) Root() string {
+ return f.root
+}
+
+// String converts this Fs to a string
+func (f *Fs) String() string {
+ return fmt.Sprintf("combine root '%s'", f.root)
+}
+
+// Features returns the optional features of this Fs
+func (f *Fs) Features() *fs.Features {
+ return f.features
+}
+
+// Rmdir removes the root directory of the Fs object
+func (f *Fs) Rmdir(ctx context.Context, dir string) error {
+ // The root always exists
+ if f.root == "" && dir == "" {
+ return nil
+ }
+ u, uRemote, err := f.findUpstream(dir)
+ if err != nil {
+ return err
+ }
+ return u.f.Rmdir(ctx, uRemote)
+}
+
+// Hashes returns hash.HashNone to indicate remote hashing is unavailable
+func (f *Fs) Hashes() hash.Set {
+ return f.hashSet
+}
+
+// Mkdir makes the root directory of the Fs object
+func (f *Fs) Mkdir(ctx context.Context, dir string) error {
+ // The root always exists
+ if f.root == "" && dir == "" {
+ return nil
+ }
+ u, uRemote, err := f.findUpstream(dir)
+ if err != nil {
+ return err
+ }
+ return u.f.Mkdir(ctx, uRemote)
+}
+
+// purge the upstream or fallback to a slow way
+func (u *upstream) purge(ctx context.Context, dir string) (err error) {
+ if do := u.f.Features().Purge; do != nil {
+ err = do(ctx, dir)
+ } else {
+ err = operations.Purge(ctx, u.f, dir)
+ }
+ return err
+}
+
+// Purge all files in the directory
+//
+// Implement this if you have a way of deleting all the files
+// quicker than just running Remove() on the result of List()
+//
+// Return an error if it doesn't exist
+func (f *Fs) Purge(ctx context.Context, dir string) error {
+ if f.root == "" && dir == "" {
+ return f.multithread(ctx, func(ctx context.Context, u *upstream) error {
+ return u.purge(ctx, "")
+ })
+ }
+ u, uRemote, err := f.findUpstream(dir)
+ if err != nil {
+ return err
+ }
+ return u.purge(ctx, uRemote)
+}
+
+// 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(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
+ srcObj, ok := src.(*Object)
+ if !ok {
+ fs.Debugf(src, "Can't copy - not same remote type")
+ return nil, fs.ErrorCantCopy
+ }
+
+ dstU, dstRemote, err := f.findUpstream(remote)
+ if err != nil {
+ return nil, err
+ }
+
+ do := dstU.f.Features().Copy
+ if do == nil {
+ return nil, fs.ErrorCantCopy
+ }
+
+ o, err := do(ctx, srcObj.Object, dstRemote)
+ if err != nil {
+ return nil, err
+ }
+
+ return dstU.newObject(o), nil
+}
+
+// Move src to this remote using server-side move 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.ErrorCantMove
+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
+ }
+
+ dstU, dstRemote, err := f.findUpstream(remote)
+ if err != nil {
+ return nil, err
+ }
+
+ do := dstU.f.Features().Move
+ useCopy := false
+ if do == nil {
+ do = dstU.f.Features().Copy
+ if do == nil {
+ return nil, fs.ErrorCantMove
+ }
+ useCopy = true
+ }
+
+ o, err := do(ctx, srcObj.Object, dstRemote)
+ if err != nil {
+ return nil, err
+ }
+
+ // If did Copy then remove the source object
+ if useCopy {
+ err = srcObj.Remove(ctx)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return dstU.newObject(o), nil
+}
+
+// DirMove moves src, srcRemote to this remote at dstRemote
+// using server-side move operations.
+//
+// Will only be called if src.Fs().Name() == f.Name()
+//
+// If it isn't possible then return fs.ErrorCantDirMove
+//
+// If destination exists then return fs.ErrorDirExists
+func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) (err error) {
+ // defer log.Trace(f, "src=%v, srcRemote=%q, dstRemote=%q", src, srcRemote, dstRemote)("err=%v", &err)
+ srcFs, ok := src.(*Fs)
+ if !ok {
+ fs.Debugf(src, "Can't move directory - not same remote type")
+ return fs.ErrorCantDirMove
+ }
+
+ dstU, dstURemote, err := f.findUpstream(dstRemote)
+ if err != nil {
+ return err
+ }
+
+ srcU, srcURemote, err := srcFs.findUpstream(srcRemote)
+ if err != nil {
+ return err
+ }
+
+ do := dstU.f.Features().DirMove
+ if do == nil {
+ return fs.ErrorCantDirMove
+ }
+
+ fs.Logf(dstU.f, "srcU.f=%v, srcURemote=%q, dstURemote=%q", srcU.f, srcURemote, dstURemote)
+ return do(ctx, srcU.f, srcURemote, dstURemote)
+}
+
+// ChangeNotify calls the passed function with a path
+// that has had changes. If the implementation
+// uses polling, it should adhere to the given interval.
+// At least one value will be written to the channel,
+// specifying the initial value and updated values might
+// follow. A 0 Duration should pause the polling.
+// The ChangeNotify implementation must empty the channel
+// regularly. When the channel gets closed, the implementation
+// should stop polling and release resources.
+func (f *Fs) ChangeNotify(ctx context.Context, notifyFunc func(string, fs.EntryType), ch <-chan time.Duration) {
+ var uChans []chan time.Duration
+
+ for _, u := range f.upstreams {
+ u := u
+ if do := u.f.Features().ChangeNotify; do != nil {
+ ch := make(chan time.Duration)
+ uChans = append(uChans, ch)
+ wrappedNotifyFunc := func(path string, entryType fs.EntryType) {
+ newPath, err := u.pathAdjustment.do(path)
+ if err != nil {
+ fs.Logf(f, "ChangeNotify: unable to process %q: %s", path, err)
+ return
+ }
+ fs.Debugf(f, "ChangeNotify: path %q entryType %d", newPath, entryType)
+ notifyFunc(newPath, entryType)
+ }
+ do(ctx, wrappedNotifyFunc, ch)
+ }
+ }
+
+ go func() {
+ for i := range ch {
+ for _, c := range uChans {
+ c <- i
+ }
+ }
+ for _, c := range uChans {
+ close(c)
+ }
+ }()
+}
+
+// DirCacheFlush resets the directory cache - used in testing
+// as an optional interface
+func (f *Fs) DirCacheFlush() {
+ ctx := context.Background()
+ _ = f.multithread(ctx, func(ctx context.Context, u *upstream) error {
+ if do := u.f.Features().DirCacheFlush; do != nil {
+ do()
+ }
+ return nil
+ })
+}
+
+func (f *Fs) put(ctx context.Context, in io.Reader, src fs.ObjectInfo, stream bool, options ...fs.OpenOption) (fs.Object, error) {
+ srcPath := src.Remote()
+ u, uRemote, err := f.findUpstream(srcPath)
+ if err != nil {
+ return nil, err
+ }
+ uSrc := operations.NewOverrideRemote(src, uRemote)
+ var o fs.Object
+ if stream {
+ o, err = u.f.Features().PutStream(ctx, in, uSrc, options...)
+ } else {
+ o, err = u.f.Put(ctx, in, uSrc, options...)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return u.newObject(o), nil
+}
+
+// Put in to the remote path with the modTime given of the given size
+//
+// May create the object even if it returns an error - if so
+// will return the object and the error, otherwise will return
+// nil and the error
+func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
+ o, err := f.NewObject(ctx, src.Remote())
+ switch err {
+ case nil:
+ return o, o.Update(ctx, in, src, options...)
+ case fs.ErrorObjectNotFound:
+ return f.put(ctx, in, src, false, options...)
+ default:
+ return nil, err
+ }
+}
+
+// PutStream uploads to the remote path with the modTime given of indeterminate size
+//
+// May create the object even if it returns an error - if so
+// will return the object and the error, otherwise will return
+// nil and the error
+func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
+ o, err := f.NewObject(ctx, src.Remote())
+ switch err {
+ case nil:
+ return o, o.Update(ctx, in, src, options...)
+ case fs.ErrorObjectNotFound:
+ return f.put(ctx, in, src, true, options...)
+ default:
+ return nil, err
+ }
+}
+
+// About gets quota information from the Fs
+func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
+ usage := &fs.Usage{
+ Total: new(int64),
+ Used: new(int64),
+ Trashed: new(int64),
+ Other: new(int64),
+ Free: new(int64),
+ Objects: new(int64),
+ }
+ for _, u := range f.upstreams {
+ doAbout := u.f.Features().About
+ if doAbout == nil {
+ continue
+ }
+ usg, err := doAbout(ctx)
+ if errors.Is(err, fs.ErrorDirNotFound) {
+ continue
+ }
+ if err != nil {
+ return nil, err
+ }
+ if usg.Total != nil && usage.Total != nil {
+ *usage.Total += *usg.Total
+ } else {
+ usage.Total = nil
+ }
+ if usg.Used != nil && usage.Used != nil {
+ *usage.Used += *usg.Used
+ } else {
+ usage.Used = nil
+ }
+ if usg.Trashed != nil && usage.Trashed != nil {
+ *usage.Trashed += *usg.Trashed
+ } else {
+ usage.Trashed = nil
+ }
+ if usg.Other != nil && usage.Other != nil {
+ *usage.Other += *usg.Other
+ } else {
+ usage.Other = nil
+ }
+ if usg.Free != nil && usage.Free != nil {
+ *usage.Free += *usg.Free
+ } else {
+ usage.Free = nil
+ }
+ if usg.Objects != nil && usage.Objects != nil {
+ *usage.Objects += *usg.Objects
+ } else {
+ usage.Objects = nil
+ }
+ }
+ return usage, nil
+}
+
+// Wraps entries for this upstream
+func (u *upstream) wrapEntries(ctx context.Context, entries fs.DirEntries) (fs.DirEntries, error) {
+ for i, entry := range entries {
+ switch x := entry.(type) {
+ case fs.Object:
+ entries[i] = u.newObject(x)
+ case fs.Directory:
+ newDir := fs.NewDirCopy(ctx, x)
+ newPath, err := u.pathAdjustment.do(newDir.Remote())
+ if err != nil {
+ return nil, err
+ }
+ newDir.SetRemote(newPath)
+ entries[i] = newDir
+ default:
+ return nil, fmt.Errorf("unknown entry type %T", entry)
+ }
+ }
+ return entries, nil
+}
+
+// List the objects and directories in dir into entries. The
+// entries can be returned in any order but should be for a
+// complete directory.
+//
+// dir should be "" to list the root, and should not have
+// trailing slashes.
+//
+// This should return ErrDirNotFound if the directory isn't
+// found.
+func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
+ // defer log.Trace(f, "dir=%q", dir)("entries = %v, err=%v", &entries, &err)
+ if f.root == "" && dir == "" {
+ entries = make(fs.DirEntries, 0, len(f.upstreams))
+ for combineDir := range f.upstreams {
+ d := fs.NewDir(combineDir, f.when)
+ entries = append(entries, d)
+ }
+ return entries, nil
+ }
+ u, uRemote, err := f.findUpstream(dir)
+ if err != nil {
+ return nil, err
+ }
+ entries, err = u.f.List(ctx, uRemote)
+ if err != nil {
+ return nil, err
+ }
+ return u.wrapEntries(ctx, entries)
+}
+
+// ListR lists the objects and directories of the Fs starting
+// from dir recursively into out.
+//
+// dir should be "" to start from the root, and should not
+// have trailing slashes.
+//
+// This should return ErrDirNotFound if the directory isn't
+// found.
+//
+// It should call callback for each tranche of entries read.
+// These need not be returned in any particular order. If
+// callback returns an error then the listing will stop
+// immediately.
+//
+// Don't implement this unless you have a more efficient way
+// of listing recursively that doing a directory traversal.
+func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) {
+ // defer log.Trace(f, "dir=%q, callback=%v", dir, callback)("err=%v", &err)
+ if f.root == "" && dir == "" {
+ rootEntries, err := f.List(ctx, "")
+ if err != nil {
+ return err
+ }
+ err = callback(rootEntries)
+ if err != nil {
+ return err
+ }
+ var mu sync.Mutex
+ syncCallback := func(entries fs.DirEntries) error {
+ mu.Lock()
+ defer mu.Unlock()
+ return callback(entries)
+ }
+ err = f.multithread(ctx, func(ctx context.Context, u *upstream) error {
+ return f.ListR(ctx, u.dir, syncCallback)
+ })
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ u, uRemote, err := f.findUpstream(dir)
+ if err != nil {
+ return err
+ }
+ wrapCallback := func(entries fs.DirEntries) error {
+ entries, err := u.wrapEntries(ctx, entries)
+ if err != nil {
+ return err
+ }
+ return callback(entries)
+ }
+ if do := u.f.Features().ListR; do != nil {
+ err = do(ctx, uRemote, wrapCallback)
+ } else {
+ err = walk.ListR(ctx, u.f, uRemote, true, -1, walk.ListAll, wrapCallback)
+ }
+ if err == fs.ErrorDirNotFound {
+ err = nil
+ }
+ return err
+}
+
+// NewObject creates a new remote combine file object
+func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
+ u, uRemote, err := f.findUpstream(remote)
+ if err != nil {
+ return nil, err
+ }
+ if uRemote == "" || strings.HasSuffix(uRemote, "/") {
+ return nil, fs.ErrorIsDir
+ }
+ o, err := u.f.NewObject(ctx, uRemote)
+ if err != nil {
+ return nil, err
+ }
+ return u.newObject(o), nil
+}
+
+// Precision is the greatest Precision of all upstreams
+func (f *Fs) Precision() time.Duration {
+ var greatestPrecision time.Duration
+ for _, u := range f.upstreams {
+ uPrecision := u.f.Precision()
+ if uPrecision > greatestPrecision {
+ greatestPrecision = uPrecision
+ }
+ }
+ return greatestPrecision
+}
+
+// Shutdown the backend, closing any background tasks and any
+// cached connections.
+func (f *Fs) Shutdown(ctx context.Context) error {
+ return f.multithread(ctx, func(ctx context.Context, u *upstream) error {
+ if do := u.f.Features().Shutdown; do != nil {
+ return do(ctx)
+ }
+ return nil
+ })
+}
+
+// Object describes a wrapped Object
+//
+// This is a wrapped Object which knows its path prefix
+type Object struct {
+ fs.Object
+ u *upstream
+}
+
+func (u *upstream) newObject(o fs.Object) *Object {
+ return &Object{
+ Object: o,
+ u: u,
+ }
+}
+
+// Fs returns read only access to the Fs that this object is part of
+func (o *Object) Fs() fs.Info {
+ return o.u.parent
+}
+
+// String returns the remote path
+func (o *Object) String() string {
+ return o.Remote()
+}
+
+// Remote returns the remote path
+func (o *Object) Remote() string {
+ newPath, err := o.u.pathAdjustment.do(o.Object.String())
+ if err != nil {
+ fs.Errorf(o, "Bad object: %v", err)
+ return err.Error()
+ }
+ return newPath
+}
+
+// MimeType returns the content type of the Object if known
+func (o *Object) MimeType(ctx context.Context) (mimeType string) {
+ if do, ok := o.Object.(fs.MimeTyper); ok {
+ mimeType = do.MimeType(ctx)
+ }
+ return mimeType
+}
+
+// UnWrap returns the Object that this Object is wrapping or
+// nil if it isn't wrapping anything
+func (o *Object) UnWrap() fs.Object {
+ return o.Object
+}
+
+// Check the interfaces are satisfied
+var (
+ _ fs.Fs = (*Fs)(nil)
+ _ fs.Purger = (*Fs)(nil)
+ _ fs.PutStreamer = (*Fs)(nil)
+ _ fs.Copier = (*Fs)(nil)
+ _ fs.Mover = (*Fs)(nil)
+ _ fs.DirMover = (*Fs)(nil)
+ _ fs.DirCacheFlusher = (*Fs)(nil)
+ _ fs.ChangeNotifier = (*Fs)(nil)
+ _ fs.Abouter = (*Fs)(nil)
+ _ fs.ListRer = (*Fs)(nil)
+ _ fs.Shutdowner = (*Fs)(nil)
+)
diff --git a/backend/combine/combine_internal_test.go b/backend/combine/combine_internal_test.go
new file mode 100644
index 0000000000000..5c889bb8c551b
--- /dev/null
+++ b/backend/combine/combine_internal_test.go
@@ -0,0 +1,94 @@
+package combine
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAdjustmentDo(t *testing.T) {
+ for _, test := range []struct {
+ root string
+ mountpoint string
+ in string
+ want string
+ wantErr error
+ }{
+ {
+ root: "",
+ mountpoint: "mountpoint",
+ in: "path/to/file.txt",
+ want: "mountpoint/path/to/file.txt",
+ },
+ {
+ root: "mountpoint",
+ mountpoint: "mountpoint",
+ in: "path/to/file.txt",
+ want: "path/to/file.txt",
+ },
+ {
+ root: "mountpoint/path",
+ mountpoint: "mountpoint",
+ in: "path/to/file.txt",
+ want: "to/file.txt",
+ },
+ {
+ root: "mountpoint/path",
+ mountpoint: "mountpoint",
+ in: "wrongpath/to/file.txt",
+ want: "",
+ wantErr: errNotUnderRoot,
+ },
+ } {
+ what := fmt.Sprintf("%+v", test)
+ a := newAdjustment(test.root, test.mountpoint)
+ got, gotErr := a.do(test.in)
+ assert.Equal(t, test.wantErr, gotErr)
+ assert.Equal(t, test.want, got, what)
+ }
+
+}
+
+func TestAdjustmentUndo(t *testing.T) {
+ for _, test := range []struct {
+ root string
+ mountpoint string
+ in string
+ want string
+ wantErr error
+ }{
+ {
+ root: "",
+ mountpoint: "mountpoint",
+ in: "mountpoint/path/to/file.txt",
+ want: "path/to/file.txt",
+ },
+ {
+ root: "mountpoint",
+ mountpoint: "mountpoint",
+ in: "path/to/file.txt",
+ want: "path/to/file.txt",
+ },
+ {
+ root: "mountpoint/path",
+ mountpoint: "mountpoint",
+ in: "to/file.txt",
+ want: "path/to/file.txt",
+ },
+ {
+ root: "wrongmountpoint/path",
+ mountpoint: "mountpoint",
+ in: "to/file.txt",
+ want: "",
+ wantErr: errNotUnderRoot,
+ },
+ } {
+ what := fmt.Sprintf("%+v", test)
+ a := newAdjustment(test.root, test.mountpoint)
+ got, gotErr := a.undo(test.in)
+ assert.Equal(t, test.wantErr, gotErr)
+ assert.Equal(t, test.want, got, what)
+ }
+
+}
diff --git a/backend/combine/combine_test.go b/backend/combine/combine_test.go
new file mode 100644
index 0000000000000..c6274c562097a
--- /dev/null
+++ b/backend/combine/combine_test.go
@@ -0,0 +1,79 @@
+// Test Combine filesystem interface
+package combine_test
+
+import (
+ "testing"
+
+ _ "github.com/rclone/rclone/backend/local"
+ _ "github.com/rclone/rclone/backend/memory"
+ "github.com/rclone/rclone/fstest"
+ "github.com/rclone/rclone/fstest/fstests"
+)
+
+// TestIntegration runs integration tests against the remote
+func TestIntegration(t *testing.T) {
+ if *fstest.RemoteName == "" {
+ t.Skip("Skipping as -remote not set")
+ }
+ fstests.Run(t, &fstests.Opt{
+ RemoteName: *fstest.RemoteName,
+ UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"},
+ UnimplementableObjectMethods: []string{"MimeType"},
+ })
+}
+
+func TestLocal(t *testing.T) {
+ if *fstest.RemoteName != "" {
+ t.Skip("Skipping as -remote set")
+ }
+ dirs := MakeTestDirs(t, 3)
+ upstreams := "dir1=" + dirs[0] + " dir2=" + dirs[1] + " dir3=" + dirs[2]
+ name := "TestCombineLocal"
+ fstests.Run(t, &fstests.Opt{
+ RemoteName: name + ":dir1",
+ ExtraConfig: []fstests.ExtraConfigItem{
+ {Name: name, Key: "type", Value: "combine"},
+ {Name: name, Key: "upstreams", Value: upstreams},
+ },
+ })
+}
+
+func TestMemory(t *testing.T) {
+ if *fstest.RemoteName != "" {
+ t.Skip("Skipping as -remote set")
+ }
+ upstreams := "dir1=:memory:dir1 dir2=:memory:dir2 dir3=:memory:dir3"
+ name := "TestCombineMemory"
+ fstests.Run(t, &fstests.Opt{
+ RemoteName: name + ":dir1",
+ ExtraConfig: []fstests.ExtraConfigItem{
+ {Name: name, Key: "type", Value: "combine"},
+ {Name: name, Key: "upstreams", Value: upstreams},
+ },
+ })
+}
+
+func TestMixed(t *testing.T) {
+ if *fstest.RemoteName != "" {
+ t.Skip("Skipping as -remote set")
+ }
+ dirs := MakeTestDirs(t, 2)
+ upstreams := "dir1=" + dirs[0] + " dir2=" + dirs[1] + " dir3=:memory:dir3"
+ name := "TestCombineMixed"
+ fstests.Run(t, &fstests.Opt{
+ RemoteName: name + ":dir1",
+ ExtraConfig: []fstests.ExtraConfigItem{
+ {Name: name, Key: "type", Value: "combine"},
+ {Name: name, Key: "upstreams", Value: upstreams},
+ },
+ })
+}
+
+// MakeTestDirs makes directories in /tmp for testing
+func MakeTestDirs(t *testing.T, n int) (dirs []string) {
+ for i := 1; i <= n; i++ {
+ dir := t.TempDir()
+ dirs = append(dirs, dir)
+ }
+ return dirs
+}
diff --git a/bin/make_manual.py b/bin/make_manual.py
index 0afa5f2b577c5..335f4c8148111 100755
--- a/bin/make_manual.py
+++ b/bin/make_manual.py
@@ -38,6 +38,7 @@
"sharefile.md",
"crypt.md",
"compress.md",
+ "combine.md",
"dropbox.md",
"filefabric.md",
"ftp.md",
diff --git a/docs/content/_index.md b/docs/content/_index.md
index 653ce53dd3d8a..c7a213bfa3970 100644
--- a/docs/content/_index.md
+++ b/docs/content/_index.md
@@ -170,6 +170,20 @@ WebDAV or S3, that work out of the box.)
{{< provider name="The local filesystem" home="/local/" config="/local/" end="true">}}
{{< /provider_list >}}
+## Virtual providers
+
+These backends adapt or modify other storage providers:
+
+{{< provider name="Alias: rename existing remotes" home="/alias/" config="/alias/" >}}
+{{< provider name="Cache: cache remotes (DEPRECATED)" home="/cache/" config="/cache/" >}}
+{{< provider name="Chunker: split large files" home="/chunker/" config="/chunker/" >}}
+{{< provider name="Combine: combine multiple remotes into a directory tree" home="/combine/" config="/combine/" >}}
+{{< provider name="Compress: compress files" home="/compress/" config="/compress/" >}}
+{{< provider name="Crypt: encrypt files" home="/crypt/" config="/crypt/" >}}
+{{< provider name="Hasher: hash files" home="/hasher/" config="/hasher/" >}}
+{{< provider name="Union: join multiple remotes to work together" home="/union/" config="/union/" >}}
+
+
## Links
* {{< icon "fa fa-home" >}} [Home page](https://rclone.org/)
diff --git a/docs/content/combine.md b/docs/content/combine.md
new file mode 100644
index 0000000000000..98e174a04de3e
--- /dev/null
+++ b/docs/content/combine.md
@@ -0,0 +1,156 @@
+---
+title: "Combine"
+description: "Combine several remotes into one"
+---
+
+# {{< icon "fa fa-folder-plus" >}} Combine
+
+The `combine` backend joins remotes together into a single directory
+tree.
+
+For example you might have a remote for images on one provider:
+
+```
+$ rclone tree s3:imagesbucket
+/
+├── image1.jpg
+└── image2.jpg
+```
+
+And a remote for files on another:
+
+```
+$ rclone tree drive:important/files
+/
+├── file1.txt
+└── file2.txt
+```
+
+The `combine` backend can join these together into a synthetic
+directory structure like this:
+
+```
+$ rclone tree combined:
+/
+├── files
+│ ├── file1.txt
+│ └── file2.txt
+└── images
+ ├── image1.jpg
+ └── image2.jpg
+```
+
+You'd do this by specifying an `upstreams` parameter in the config
+like this
+
+ upstreams = images=s3:imagesbucket files=drive:important/files
+
+During the initial setup with `rclone config` you will specify the
+upstreams remotes as a space separated list. The upstream remotes can
+either be a local paths or other remotes.
+
+## Configuration
+
+Here is an example of how to make a combine called `remote` for the
+example above. First run:
+
+ rclone config
+
+This will guide you through an interactive setup process:
+
+```
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+name> remote
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+...
+XX / Combine several remotes into one
+ \ (combine)
+...
+Storage> combine
+Option upstreams.
+Upstreams for combining
+These should be in the form
+ dir=remote:path dir2=remote2:path
+Where before the = is specified the root directory and after is the remote to
+put there.
+Embedded spaces can be added using quotes
+ "dir=remote:path with space" "dir2=remote2:path with space"
+Enter a fs.SpaceSepList value.
+upstreams> images=s3:imagesbucket files=drive:important/files
+--------------------
+[remote]
+type = combine
+upstreams = images=s3:imagesbucket files=drive:important/files
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+```
+
+### Configuring for Google Drive Shared Drives
+
+Rclone has a convenience feature for making a combine backend for all
+the shared drives you have access to.
+
+Assuming your main (non shared drive) Google drive remote is called
+`drive:` you would run
+
+ rclone backend -o config drives drive:
+
+This would produce something like this:
+
+ [My Drive]
+ type = alias
+ remote = drive,team_drive=0ABCDEF-01234567890,root_folder_id=:
+
+ [Test Drive]
+ type = alias
+ remote = drive,team_drive=0ABCDEFabcdefghijkl,root_folder_id=:
+
+ [AllDrives]
+ type = combine
+ remote = "My Drive=My Drive:" "Test Drive=Test Drive:"
+
+If you then add that config to your config file (find it with `rclone
+config file`) then you can access all the shared drives in one place
+with the `AllDrives:` remote.
+
+See [the Google Drive docs](/drive/#drives) for full info.
+
+{{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/combine/combine.go then run make backenddocs" >}}
+### Standard options
+
+Here are the standard options specific to combine (Combine several remotes into one).
+
+#### --combine-upstreams
+
+Upstreams for combining
+
+These should be in the form
+
+ dir=remote:path dir2=remote2:path
+
+Where before the = is specified the root directory and after is the remote to
+put there.
+
+Embedded spaces can be added using quotes
+
+ "dir=remote:path with space" "dir2=remote2:path with space"
+
+
+
+Properties:
+
+- Config: upstreams
+- Env Var: RCLONE_COMBINE_UPSTREAMS
+- Type: SpaceSepList
+- Default:
+
+{{< rem autogenerated options stop >}}
diff --git a/docs/content/docs.md b/docs/content/docs.md
index ac714c0f7fd4d..9be5038b8f42f 100644
--- a/docs/content/docs.md
+++ b/docs/content/docs.md
@@ -37,6 +37,7 @@ See the following for detailed instructions for
* [Chunker](/chunker/) - transparently splits large files for other remotes
* [Citrix ShareFile](/sharefile/)
* [Compress](/compress/)
+ * [Combine](/combine/)
* [Crypt](/crypt/) - to encrypt other remotes
* [DigitalOcean Spaces](/s3/#digitalocean-spaces)
* [Digi Storage](/koofr/#digi-storage)
diff --git a/docs/layouts/chrome/navbar.html b/docs/layouts/chrome/navbar.html
index 552b976cac986..32c05d3e0c346 100644
--- a/docs/layouts/chrome/navbar.html
+++ b/docs/layouts/chrome/navbar.html
@@ -60,6 +60,7 @@
Box
Chunker (splits large files)
Compress (transparent gzip compression)
+ Combine (remotes into a directory tree)
Citrix ShareFile
Crypt (encrypts the others)
Digi Storage
diff --git a/fstest/test_all/config.yaml b/fstest/test_all/config.yaml
index 1b6bb9e1dc7bf..f0abff00739e9 100644
--- a/fstest/test_all/config.yaml
+++ b/fstest/test_all/config.yaml
@@ -91,6 +91,9 @@ backends:
fastlist: true
maxfile: 1k
## end chunker
+ - backend: "combine"
+ remote: "TestCombine:dir1"
+ fastlist: false
## begin compress
- backend: "compress"
remote: "TestCompress:"
From 20aaeba547d802199f179ca7f704efef2a665450 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 8 Jun 2022 16:29:35 +0200
Subject: [PATCH 008/560] docs: clarify backend support for setting modtime
only (#5638)
---
docs/content/overview.md | 130 ++++++++++++++++++++++++---------------
1 file changed, 81 insertions(+), 49 deletions(-)
diff --git a/docs/content/overview.md b/docs/content/overview.md
index 73974b65b2870..b652ac7558a7b 100644
--- a/docs/content/overview.md
+++ b/docs/content/overview.md
@@ -14,48 +14,48 @@ show through.
Here is an overview of the major features of each cloud storage system.
-| Name | Hash | ModTime | Case Insensitive | Duplicate Files | MIME Type |
-| ---------------------------- |:-----------:|:-------:|:----------------:|:---------------:|:---------:|
-| 1Fichier | Whirlpool | No | No | Yes | R |
-| Akamai Netstorage | MD5, SHA256 | Yes | No | No | R |
-| Amazon Drive | MD5 | No | Yes | No | R |
-| Amazon S3 (or S3 compatible) | MD5 | Yes | No | No | R/W |
-| Backblaze B2 | SHA1 | Yes | No | No | R/W |
-| Box | SHA1 | Yes | Yes | No | - |
-| Citrix ShareFile | MD5 | Yes | Yes | No | - |
-| Dropbox | DBHASH ¹ | Yes | Yes | No | - |
-| Enterprise File Fabric | - | Yes | Yes | No | R/W |
-| FTP | - | No | No | No | - |
-| Google Cloud Storage | MD5 | Yes | No | No | R/W |
-| Google Drive | MD5 | Yes | No | Yes | R/W |
-| Google Photos | - | No | No | Yes | R |
-| HDFS | - | Yes | No | No | - |
-| HTTP | - | No | No | No | R |
-| Hubic | MD5 | Yes | No | No | R/W |
-| Internet Archive | MD5, SHA1, CRC32 | Yes | No | No | - |
-| Jottacloud | MD5 | Yes | Yes | No | R |
-| Koofr | MD5 | No | Yes | No | - |
-| Mail.ru Cloud | Mailru ⁶ | Yes | Yes | No | - |
-| Mega | - | No | No | Yes | - |
-| Memory | MD5 | Yes | No | No | - |
-| Microsoft Azure Blob Storage | MD5 | Yes | No | No | R/W |
-| Microsoft OneDrive | SHA1 ⁵ | Yes | Yes | No | R |
-| OpenDrive | MD5 | Yes | Yes | Partial ⁸ | - |
-| OpenStack Swift | MD5 | Yes | No | No | R/W |
-| pCloud | MD5, SHA1 ⁷ | Yes | No | No | W |
-| premiumize.me | - | No | Yes | No | R |
-| put.io | CRC-32 | Yes | No | Yes | R |
-| QingStor | MD5 | No | No | No | R/W |
-| Seafile | - | No | No | No | - |
-| SFTP | MD5, SHA1 ² | Yes | Depends | No | - |
-| Sia | - | No | No | No | - |
-| SugarSync | - | No | No | No | - |
-| Storj | - | Yes | No | No | - |
-| Uptobox | - | No | No | Yes | - |
-| WebDAV | MD5, SHA1 ³ | Yes ⁴ | Depends | No | - |
-| Yandex Disk | MD5 | Yes | No | No | R |
-| Zoho WorkDrive | - | No | No | No | - |
-| The local filesystem | All | Yes | Depends | No | - |
+| Name | Hash | ModTime | Case Insensitive | Duplicate Files | MIME Type |
+| ---------------------------- |:----------------:|:-------:|:----------------:|:---------------:|:---------:|
+| 1Fichier | Whirlpool | - | No | Yes | R |
+| Akamai Netstorage | MD5, SHA256 | R/W | No | No | R |
+| Amazon Drive | MD5 | - | Yes | No | R |
+| Amazon S3 (or S3 compatible) | MD5 | R/W | No | No | R/W |
+| Backblaze B2 | SHA1 | R/W | No | No | R/W |
+| Box | SHA1 | R/W | Yes | No | - |
+| Citrix ShareFile | MD5 | R/W | Yes | No | - |
+| Dropbox | DBHASH ¹ | R | Yes | No | - |
+| Enterprise File Fabric | - | R/W | Yes | No | R/W |
+| FTP | - | R/W ¹⁰ | No | No | - |
+| Google Cloud Storage | MD5 | R/W | No | No | R/W |
+| Google Drive | MD5 | R/W | No | Yes | R/W |
+| Google Photos | - | - | No | Yes | R |
+| HDFS | - | R/W | No | No | - |
+| HTTP | - | R | No | No | R |
+| Hubic | MD5 | R/W | No | No | R/W |
+| Internet Archive | MD5, SHA1, CRC32 | R/W ¹¹ | No | No | - |
+| Jottacloud | MD5 | R/W | Yes | No | R |
+| Koofr | MD5 | - | Yes | No | - |
+| Mail.ru Cloud | Mailru ⁶ | R/W | Yes | No | - |
+| Mega | - | - | No | Yes | - |
+| Memory | MD5 | R/W | No | No | - |
+| Microsoft Azure Blob Storage | MD5 | R/W | No | No | R/W |
+| Microsoft OneDrive | SHA1 ⁵ | R/W | Yes | No | R |
+| OpenDrive | MD5 | R/W | Yes | Partial ⁸ | - |
+| OpenStack Swift | MD5 | R/W | No | No | R/W |
+| pCloud | MD5, SHA1 ⁷ | R | No | No | W |
+| premiumize.me | - | - | Yes | No | R |
+| put.io | CRC-32 | R/W | No | Yes | R |
+| QingStor | MD5 | - ⁹ | No | No | R/W |
+| Seafile | - | - | No | No | - |
+| SFTP | MD5, SHA1 ² | R/W | Depends | No | - |
+| Sia | - | - | No | No | - |
+| SugarSync | - | - | No | No | - |
+| Storj | - | R | No | No | - |
+| Uptobox | - | - | No | Yes | - |
+| WebDAV | MD5, SHA1 ³ | R ⁴ | Depends | No | - |
+| Yandex Disk | MD5 | R/W | No | No | R |
+| Zoho WorkDrive | - | - | No | No | - |
+| The local filesystem | All | R/W | Depends | No | - |
### Notes
@@ -84,6 +84,15 @@ storage platform has been determined to allow duplicate files, and it
is possible to create them with `rclone`. It may be that this is a
mistake or an unsupported feature.
+⁹ QingStor does not support SetModTime for objects bigger than 5 GiB.
+
+¹⁰ FTP supports modtimes for the major FTP servers, and also others
+if they advertised required protocol extensions. See [this](/ftp/#modified-time)
+for more details.
+
+¹¹ Internet Archive requires option `wait_archive` to be set to a non-zero value
+for full modtime support.
+
### Hash ###
The cloud storage system supports various hash types of the objects.
@@ -96,13 +105,36 @@ systems they must support a common hash type.
### ModTime ###
-The cloud storage system supports setting modification times on
-objects. If it does then this enables a using the modification times
-as part of the sync. If not then only the size will be checked by
-default, though the MD5SUM can be checked with the `--checksum` flag.
-
-All cloud storage systems support some kind of date on the object and
-these will be set when transferring from the cloud storage system.
+Allmost all cloud storage systems store some sort of timestamp
+on objects, but several of them not something that is appropriate
+to use for syncing. E.g. some backends will only write a timestamp
+that represent the time of the upload. To be relevant for syncing
+it should be able to store the modification time of the source
+object. If this is not the case, rclone will only check the file
+size by default, though can be configured to check the file hash
+(with the `--checksum` flag). Ideally it should also be possible to
+change the timestamp of an existing file without having to re-upload it.
+
+Storage systems with a `-` in the ModTime column, means the
+modification read on objects is not the modification time of the
+file when uploaded. It is most likely the time the file was uploaded,
+or possibly something else (like the time the picture was taken in
+Google Photos).
+
+Storage systems with a `R` (for read-only) in the ModTime column,
+means the it keeps modification times on objects, and updates them
+when uploading objects, but it does not support changing only the
+modification time (`SetModTime` operation) without re-uploading,
+possibly not even without deleting existing first. Some operations
+in rclone, such as `copy` and `sync` commands, will automatically
+check for `SetModTime` support and re-upload if necessary to keep
+the modification times in sync. Other commands will not work
+without `SetModTime` support, e.g. `touch` command on an existing
+file will fail, and changes to modification time only on a files
+in a `mount` will be silently ignored.
+
+Storage systems with `R/W` (for read/write) in the ModTime column,
+means they do also support modtime-only operations.
### Case Insensitive ###
From f4f0e444bf60b7bf6ebac9fa1e8a94d6821bdd40 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 8 Jun 2022 09:29:01 +0200
Subject: [PATCH 009/560] filter: allow multiple --exclude-if-present flags -
fixes #6219
---
docs/content/docs.md | 1 +
docs/content/filtering.md | 6 +++---
docs/content/flags.md | 2 +-
fs/filter/filter.go | 22 +++++++++++++---------
fs/filter/filterflags/filterflags.go | 2 +-
fs/operations/listdirsorted_test.go | 4 ++--
fs/operations/operations_test.go | 2 +-
fs/sync/sync_test.go | 2 +-
fs/walk/walk.go | 10 ++++++----
fs/walk/walk_test.go | 4 ++--
10 files changed, 31 insertions(+), 24 deletions(-)
diff --git a/docs/content/docs.md b/docs/content/docs.md
index 9be5038b8f42f..50bc83cfae7a0 100644
--- a/docs/content/docs.md
+++ b/docs/content/docs.md
@@ -2136,6 +2136,7 @@ For the filtering options
* `--filter-from`
* `--exclude`
* `--exclude-from`
+ * `--exclude-if-present`
* `--include`
* `--include-from`
* `--files-from`
diff --git a/docs/content/filtering.md b/docs/content/filtering.md
index f306accf51d2d..de191ddc22ca4 100644
--- a/docs/content/filtering.md
+++ b/docs/content/filtering.md
@@ -750,7 +750,9 @@ Useful for debugging.
The `--exclude-if-present` flag controls whether a directory is
within the scope of an rclone command based on the presence of a
-named file within it.
+named file within it. The flag can be repeated to check for
+multiple file names, presence of any of them will exclude the
+directory.
This flag has a priority over other filter flags.
@@ -764,8 +766,6 @@ E.g. for the following directory structure:
The command `rclone ls --exclude-if-present .ignore dir1` does
not list `dir3`, `file3` or `.ignore`.
-`--exclude-if-present` can only be used once in an rclone command.
-
## Common pitfalls
The most frequent filter support issues on
diff --git a/docs/content/flags.md b/docs/content/flags.md
index 69a0ea380ff1b..bfc367e63afdc 100644
--- a/docs/content/flags.md
+++ b/docs/content/flags.md
@@ -47,7 +47,7 @@ These flags are available for every command.
--error-on-no-transfer Sets exit code 9 if no files are transferred, useful in scripts
--exclude stringArray Exclude files matching pattern
--exclude-from stringArray Read exclude patterns from file (use - to read from stdin)
- --exclude-if-present string Exclude directories if filename is present
+ --exclude-if-present stringArray Exclude directories if filename is present
--expect-continue-timeout duration Timeout when using expect / 100-continue in HTTP (default 1s)
--fast-list Use recursive list if available; uses more memory but fewer transactions
--files-from stringArray Read list of source-file names from file (use - to read from stdin)
diff --git a/fs/filter/filter.go b/fs/filter/filter.go
index 72a25462f1b46..37071f9d26646 100644
--- a/fs/filter/filter.go
+++ b/fs/filter/filter.go
@@ -86,7 +86,7 @@ type Opt struct {
FilterFrom []string
ExcludeRule []string
ExcludeFrom []string
- ExcludeFile string
+ ExcludeFile []string
IncludeRule []string
IncludeFrom []string
FilesFrom []string
@@ -392,8 +392,10 @@ func (f *Filter) ListContainsExcludeFile(entries fs.DirEntries) bool {
obj, ok := entry.(fs.Object)
if ok {
basename := path.Base(obj.Remote())
- if basename == f.Opt.ExcludeFile {
- return true
+ for _, excludeFile := range f.Opt.ExcludeFile {
+ if basename == excludeFile {
+ return true
+ }
}
}
}
@@ -436,12 +438,14 @@ func (f *Filter) IncludeDirectory(ctx context.Context, fs fs.Fs) func(string) (b
// empty string (for testing).
func (f *Filter) DirContainsExcludeFile(ctx context.Context, fremote fs.Fs, remote string) (bool, error) {
if len(f.Opt.ExcludeFile) > 0 {
- exists, err := fs.FileExists(ctx, fremote, path.Join(remote, f.Opt.ExcludeFile))
- if err != nil {
- return false, err
- }
- if exists {
- return true, nil
+ for _, excludeFile := range f.Opt.ExcludeFile {
+ exists, err := fs.FileExists(ctx, fremote, path.Join(remote, excludeFile))
+ if err != nil {
+ return false, err
+ }
+ if exists {
+ return true, nil
+ }
}
}
return false, nil
diff --git a/fs/filter/filterflags/filterflags.go b/fs/filter/filterflags/filterflags.go
index e926fd8946a28..3963e0c04d476 100644
--- a/fs/filter/filterflags/filterflags.go
+++ b/fs/filter/filterflags/filterflags.go
@@ -34,7 +34,7 @@ func AddFlags(flagSet *pflag.FlagSet) {
flags.StringArrayVarP(flagSet, &Opt.FilterFrom, "filter-from", "", nil, "Read filtering patterns from a file (use - to read from stdin)")
flags.StringArrayVarP(flagSet, &Opt.ExcludeRule, "exclude", "", nil, "Exclude files matching pattern")
flags.StringArrayVarP(flagSet, &Opt.ExcludeFrom, "exclude-from", "", nil, "Read exclude patterns from file (use - to read from stdin)")
- flags.StringVarP(flagSet, &Opt.ExcludeFile, "exclude-if-present", "", "", "Exclude directories if filename is present")
+ flags.StringArrayVarP(flagSet, &Opt.ExcludeFile, "exclude-if-present", "", nil, "Exclude directories if filename is present")
flags.StringArrayVarP(flagSet, &Opt.IncludeRule, "include", "", nil, "Include files matching pattern")
flags.StringArrayVarP(flagSet, &Opt.IncludeFrom, "include-from", "", nil, "Read include patterns from file (use - to read from stdin)")
flags.StringArrayVarP(flagSet, &Opt.FilesFrom, "files-from", "", nil, "Read list of source-file names from file (use - to read from stdin)")
diff --git a/fs/operations/listdirsorted_test.go b/fs/operations/listdirsorted_test.go
index ae09ab4c9534a..5a4e3eeeade1a 100644
--- a/fs/operations/listdirsorted_test.go
+++ b/fs/operations/listdirsorted_test.go
@@ -81,7 +81,7 @@ func TestListDirSorted(t *testing.T) {
assert.Equal(t, "sub dir/sub sub dir/", str(1))
// testing ignore file
- fi.Opt.ExcludeFile = ".ignore"
+ fi.Opt.ExcludeFile = []string{".ignore"}
items, err = list.DirSorted(context.Background(), r.Fremote, false, "sub dir")
require.NoError(t, err)
@@ -98,7 +98,7 @@ func TestListDirSorted(t *testing.T) {
assert.Equal(t, "sub dir/ignore dir/.ignore", str(0))
assert.Equal(t, "sub dir/ignore dir/should be ignored", str(1))
- fi.Opt.ExcludeFile = ""
+ fi.Opt.ExcludeFile = nil
items, err = list.DirSorted(context.Background(), r.Fremote, false, "sub dir/ignore dir")
require.NoError(t, err)
require.Len(t, items, 2)
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 363d88f59d1b7..83df26398a5e6 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -1309,7 +1309,7 @@ func TestOverlappingFilterCheckWithFilter(t *testing.T) {
fi, err := filter.NewFilter(nil)
require.NoError(t, err)
require.NoError(t, fi.Add(false, "*/exclude/"))
- fi.Opt.ExcludeFile = ".ignore"
+ fi.Opt.ExcludeFile = []string{".ignore"}
ctx = filter.ReplaceConfig(ctx, fi)
src := &testFs{testFsInfo{name: "name", root: "root"}}
diff --git a/fs/sync/sync_test.go b/fs/sync/sync_test.go
index bd0772bd64bfd..19e225fd92a76 100644
--- a/fs/sync/sync_test.go
+++ b/fs/sync/sync_test.go
@@ -1452,7 +1452,7 @@ func TestSyncOverlapWithFilter(t *testing.T) {
require.NoError(t, err)
require.NoError(t, fi.Add(false, "/rclone-sync-test/"))
require.NoError(t, fi.Add(false, "*/layer2/"))
- fi.Opt.ExcludeFile = ".ignore"
+ fi.Opt.ExcludeFile = []string{".ignore"}
ctx = filter.ReplaceConfig(ctx, fi)
subRemoteName := r.FremoteName + "/rclone-sync-test"
diff --git a/fs/walk/walk.go b/fs/walk/walk.go
index 5f93cca493f98..b6ca99c325e52 100644
--- a/fs/walk/walk.go
+++ b/fs/walk/walk.go
@@ -507,10 +507,12 @@ func walkRDirTree(ctx context.Context, f fs.Fs, startPath string, includeAll boo
// Check if we need to prune a directory later.
if !includeAll && len(fi.Opt.ExcludeFile) > 0 {
basename := path.Base(x.Remote())
- if basename == fi.Opt.ExcludeFile {
- excludeDir := parentDir(x.Remote())
- toPrune[excludeDir] = true
- fs.Debugf(basename, "Excluded from sync (and deletion) based on exclude file")
+ for _, excludeFile := range fi.Opt.ExcludeFile {
+ if basename == excludeFile {
+ excludeDir := parentDir(x.Remote())
+ toPrune[excludeDir] = true
+ fs.Debugf(basename, "Excluded from sync (and deletion) based on exclude file")
+ }
}
}
case fs.Directory:
diff --git a/fs/walk/walk_test.go b/fs/walk/walk_test.go
index ecfa3ed7bc635..cbfa82ce830b2 100644
--- a/fs/walk/walk_test.go
+++ b/fs/walk/walk_test.go
@@ -736,13 +736,13 @@ b/c/d/
e
`, nil, "", -1, "ign", true},
} {
- fi.Opt.ExcludeFile = test.excludeFile
+ fi.Opt.ExcludeFile = []string{test.excludeFile}
r, err := walkRDirTree(context.Background(), nil, test.root, test.includeAll, test.level, makeListRCallback(test.entries, test.err))
assert.Equal(t, test.err, err, fmt.Sprintf("%+v", test))
assert.Equal(t, test.want, r.String(), fmt.Sprintf("%+v", test))
}
// Set to default value, to avoid side effects
- fi.Opt.ExcludeFile = ""
+ fi.Opt.ExcludeFile = nil
}
func TestListType(t *testing.T) {
From 700ca23a718adeb9b8cc6671a065ebe2f347cb3d Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Tue, 18 Jan 2022 22:38:24 +0100
Subject: [PATCH 010/560] config: add utility function for backend config with
list and custom input
---
backend/jottacloud/jottacloud.go | 6 +--
backend/onedrive/onedrive.go | 2 +-
fs/backend_config.go | 75 +++++++++++++++++++++++++++++---
3 files changed, 74 insertions(+), 9 deletions(-)
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index c75774d93f643..3322d8d8c3a6b 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -127,7 +127,7 @@ func init() {
func Config(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
switch config.State {
case "":
- return fs.ConfigChooseFixed("auth_type_done", "config_type", `Authentication type.`, []fs.OptionExample{{
+ return fs.ConfigChooseExclusiveFixed("auth_type_done", "config_type", `Authentication type.`, []fs.OptionExample{{
Value: "standard",
Help: "Standard authentication.\nUse this if you're a normal Jottacloud user.",
}, {
@@ -286,7 +286,7 @@ machines.`)
if err != nil {
return nil, err
}
- return fs.ConfigChoose("choose_device_result", "config_device", `Please select the device to use. Normally this will be Jotta`, len(acc.Devices), func(i int) (string, string) {
+ return fs.ConfigChooseExclusive("choose_device_result", "config_device", `Please select the device to use. Normally this will be Jotta`, len(acc.Devices), func(i int) (string, string) {
return acc.Devices[i].Name, ""
})
case "choose_device_result":
@@ -304,7 +304,7 @@ machines.`)
if err != nil {
return nil, err
}
- return fs.ConfigChoose("choose_device_mountpoint", "config_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) {
+ return fs.ConfigChooseExclusive("choose_device_mountpoint", "config_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) {
return dev.MountPoints[i].Name, ""
})
case "choose_device_mountpoint":
diff --git a/backend/onedrive/onedrive.go b/backend/onedrive/onedrive.go
index 80bf340c607e3..132ed77e66a9c 100644
--- a/backend/onedrive/onedrive.go
+++ b/backend/onedrive/onedrive.go
@@ -424,7 +424,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
switch config.State {
case "choose_type":
- return fs.ConfigChooseFixed("choose_type_done", "config_type", "Type of connection", []fs.OptionExample{{
+ return fs.ConfigChooseExclusiveFixed("choose_type_done", "config_type", "Type of connection", []fs.OptionExample{{
Value: "onedrive",
Help: "OneDrive Personal or Business",
}, {
diff --git a/fs/backend_config.go b/fs/backend_config.go
index cef15bf56a456..1e188a0ac1300 100644
--- a/fs/backend_config.go
+++ b/fs/backend_config.go
@@ -176,7 +176,13 @@ func ConfigConfirm(state string, Default bool, name string, help string) (*Confi
}, nil
}
-// ConfigChooseFixed returns a ConfigOut structure which has a list of items to choose from.
+// ConfigChooseExclusiveFixed returns a ConfigOut structure which has a list of
+// items to choose from.
+//
+// Possible items must be supplied as a fixed list.
+//
+// User is required to supply a value, and is restricted to the specified list,
+// i.e. free text input is not allowed.
//
// state should be the next state required
// name is the config name for this item
@@ -185,8 +191,8 @@ func ConfigConfirm(state string, Default bool, name string, help string) (*Confi
//
// It chooses the first item to be the default.
// If there are no items then it will return an error.
-// If there is only one item it will short cut to the next state
-func ConfigChooseFixed(state string, name string, help string, items []OptionExample) (*ConfigOut, error) {
+// If there is only one item it will short cut to the next state.
+func ConfigChooseExclusiveFixed(state string, name string, help string, items []OptionExample) (*ConfigOut, error) {
if len(items) == 0 {
return nil, fmt.Errorf("no items found in: %s", help)
}
@@ -208,7 +214,13 @@ func ConfigChooseFixed(state string, name string, help string, items []OptionExa
return choose, nil
}
-// ConfigChoose returns a ConfigOut structure which has a list of items to choose from.
+// ConfigChooseExclusive returns a ConfigOut structure which has a list of
+// items to choose from.
+//
+// Possible items are retrieved from a supplied function.
+//
+// User is required to supply a value, and is restricted to the specified list,
+// i.e. free text input is not allowed.
//
// state should be the next state required
// name is the config name for this item
@@ -218,7 +230,60 @@ func ConfigChooseFixed(state string, name string, help string, items []OptionExa
//
// It chooses the first item to be the default.
// If there are no items then it will return an error.
-// If there is only one item it will short cut to the next state
+// If there is only one item it will short cut to the next state.
+func ConfigChooseExclusive(state string, name string, help string, n int, getItem func(i int) (itemValue string, itemHelp string)) (*ConfigOut, error) {
+ items := make(OptionExamples, n)
+ for i := range items {
+ items[i].Value, items[i].Help = getItem(i)
+ }
+ return ConfigChooseExclusiveFixed(state, name, help, items)
+}
+
+// ConfigChooseFixed returns a ConfigOut structure which has a list of
+// suggested items.
+//
+// Suggested items must be supplied as a fixed list.
+//
+// User is required to supply a value, but is not restricted to the specified
+// list, i.e. free text input is accepted.
+//
+// state should be the next state required
+// name is the config name for this item
+// help should be the help shown to the user
+// items should be the items in the list
+//
+// It chooses the first item to be the default.
+func ConfigChooseFixed(state string, name string, help string, items []OptionExample) (*ConfigOut, error) {
+ choose := &ConfigOut{
+ State: state,
+ Option: &Option{
+ Name: name,
+ Help: help,
+ Examples: items,
+ Required: true,
+ },
+ }
+ if len(choose.Option.Examples) > 0 {
+ choose.Option.Default = choose.Option.Examples[0].Value
+ }
+ return choose, nil
+}
+
+// ConfigChoose returns a ConfigOut structure which has a list of suggested
+// items.
+//
+// Suggested items are retrieved from a supplied function.
+//
+// User is required to supply a value, but is not restricted to the specified
+// list, i.e. free text input is accepted.
+//
+// state should be the next state required
+// name is the config name for this item
+// help should be the help shown to the user
+// n should be the number of items in the list
+// getItem should return the items (value, help)
+//
+// It chooses the first item to be the default.
func ConfigChoose(state string, name string, help string, n int, getItem func(i int) (itemValue string, itemHelp string)) (*ConfigOut, error) {
items := make(OptionExamples, n)
for i := range items {
From 01340acad24bc07f8716c0924d6ecfdfca20f1be Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Tue, 18 Jan 2022 20:59:50 +0100
Subject: [PATCH 011/560] jottacloud: add support for upload to custom device
and mountpoint
See #5926
---
backend/jottacloud/jottacloud.go | 151 ++++++++++++++++++++++++++++---
docs/content/jottacloud.md | 115 ++++++++++++++---------
2 files changed, 212 insertions(+), 54 deletions(-)
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index 3322d8d8c3a6b..65df99f585915 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -127,7 +127,7 @@ func init() {
func Config(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
switch config.State {
case "":
- return fs.ConfigChooseExclusiveFixed("auth_type_done", "config_type", `Authentication type.`, []fs.OptionExample{{
+ return fs.ConfigChooseExclusiveFixed("auth_type_done", "config_type", `Select authentication type.`, []fs.OptionExample{{
Value: "standard",
Help: "Standard authentication.\nUse this if you're a normal Jottacloud user.",
}, {
@@ -145,7 +145,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
return fs.ConfigGoto(config.Result)
case "standard": // configure a jottacloud backend using the modern JottaCli token based authentication
m.Set("configVersion", fmt.Sprint(configVersion))
- return fs.ConfigInput("standard_token", "config_login_token", "Personal login token.\n\nGenerate here: https://www.jottacloud.com/web/secure")
+ return fs.ConfigInput("standard_token", "config_login_token", "Personal login token.\nGenerate here: https://www.jottacloud.com/web/secure")
case "standard_token":
loginToken := config.Result
m.Set(configClientID, defaultClientID)
@@ -262,7 +262,11 @@ machines.`)
},
})
case "choose_device":
- return fs.ConfigConfirm("choose_device_query", false, "config_non_standard", "Use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?")
+ return fs.ConfigConfirm("choose_device_query", false, "config_non_standard", `Use a non-standard device/mountpoint?
+Choosing no, the default, will let you access the storage used for the archive
+section of the official Jottacloud client. If you instead want to access the
+sync or the backup section, for example, you must choose yes.`)
+
case "choose_device_query":
if config.Result != "true" {
m.Set(configDevice, "")
@@ -286,12 +290,27 @@ machines.`)
if err != nil {
return nil, err
}
- return fs.ConfigChooseExclusive("choose_device_result", "config_device", `Please select the device to use. Normally this will be Jotta`, len(acc.Devices), func(i int) (string, string) {
- return acc.Devices[i].Name, ""
+
+ deviceNames := make([]string, len(acc.Devices))
+ for i, dev := range acc.Devices {
+ if i > 0 && dev.Name == defaultDevice {
+ // Insert the special Jotta device as first entry, making it the default choice.
+ copy(deviceNames[1:i+1], deviceNames[0:i])
+ deviceNames[0] = dev.Name
+ } else {
+ deviceNames[i] = dev.Name
+ }
+ }
+
+ help := fmt.Sprintf(`The device to use. In standard setup the built-in %s device is used,
+which contains predefined mountpoints for archive, sync etc. All other devices
+are treated as backup devices by the official Jottacloud client. You may create
+a new by entering a unique name.`, defaultDevice)
+ return fs.ConfigChoose("choose_device_result", "config_device", help, len(deviceNames), func(i int) (string, string) {
+ return deviceNames[i], ""
})
case "choose_device_result":
device := config.Result
- m.Set(configDevice, device)
oAuthClient, _, err := getOAuthClient(ctx, name, m)
if err != nil {
@@ -300,16 +319,89 @@ machines.`)
srv := rest.NewClient(oAuthClient).SetRoot(rootURL)
username, _ := m.Get(configUsername)
- dev, err := getDeviceInfo(ctx, srv, path.Join(username, device))
+
+ acc, err := getDriveInfo(ctx, srv, username)
if err != nil {
return nil, err
}
- return fs.ConfigChooseExclusive("choose_device_mountpoint", "config_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) {
+ isNew := true
+ for _, dev := range acc.Devices {
+ if strings.EqualFold(dev.Name, device) { // If device name exists with different casing we prefer the existing (not sure if and how the api handles the opposite)
+ device = dev.Name // Prefer same casing as existing, e.g. if user entered "jotta" we use the standard casing "Jotta" instead
+ isNew = false
+ break
+ }
+ }
+ var dev *api.JottaDevice
+ if isNew {
+ fs.Debugf(nil, "Creating new device: %s", device)
+ dev, err = createDevice(ctx, srv, path.Join(username, device))
+ if err != nil {
+ return nil, err
+ }
+ }
+ m.Set(configDevice, device)
+
+ if !isNew {
+ dev, err = getDeviceInfo(ctx, srv, path.Join(username, device))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var help string
+ if device == defaultDevice {
+ // With built-in Jotta device the mountpoint choice is exclusive,
+ // we do not want to risk any problems by creating new mountpoints on it.
+ help = fmt.Sprintf(`The mountpoint to use on the built-in device %s.
+The standard setup is to use the %s mountpoint. Most other mountpoints
+have very limited support in rclone and should generally be avoided.`, defaultDevice, defaultMountpoint)
+ return fs.ConfigChooseExclusive("choose_device_mountpoint", "config_mountpoint", help, len(dev.MountPoints), func(i int) (string, string) {
+ return dev.MountPoints[i].Name, ""
+ })
+ }
+ help = fmt.Sprintf(`The mountpoint to use on the non-standard device %s.
+You may create a new by entering a unique name.`, device)
+ return fs.ConfigChoose("choose_device_mountpoint", "config_mountpoint", help, len(dev.MountPoints), func(i int) (string, string) {
return dev.MountPoints[i].Name, ""
})
case "choose_device_mountpoint":
mountpoint := config.Result
+
+ oAuthClient, _, err := getOAuthClient(ctx, name, m)
+ if err != nil {
+ return nil, err
+ }
+ srv := rest.NewClient(oAuthClient).SetRoot(rootURL)
+
+ username, _ := m.Get(configUsername)
+ device, _ := m.Get(configDevice)
+
+ dev, err := getDeviceInfo(ctx, srv, path.Join(username, device))
+ if err != nil {
+ return nil, err
+ }
+ isNew := true
+ for _, mnt := range dev.MountPoints {
+ if strings.EqualFold(mnt.Name, mountpoint) {
+ mountpoint = mnt.Name
+ isNew = false
+ break
+ }
+ }
+
+ if isNew {
+ if device == defaultDevice {
+ return nil, fmt.Errorf("custom mountpoints not supported on built-in %s device: %w", defaultDevice, err)
+ }
+ fs.Debugf(nil, "Creating new mountpoint: %s", mountpoint)
+ _, err := createMountPoint(ctx, srv, path.Join(username, device, mountpoint))
+ if err != nil {
+ return nil, err
+ }
+ }
m.Set(configMountpoint, mountpoint)
+
return fs.ConfigGoto("end")
case "end":
// All the config flows end up here in case we need to carry on with something
@@ -338,6 +430,7 @@ type Fs struct {
opt Options
features *fs.Features
endpointURL string
+ allocateURL string
srv *rest.Client
apiSrv *rest.Client
pacer *fs.Pacer
@@ -588,6 +681,37 @@ func getDeviceInfo(ctx context.Context, srv *rest.Client, path string) (info *ap
return info, nil
}
+// createDevice makes a device
+func createDevice(ctx context.Context, srv *rest.Client, path string) (info *api.JottaDevice, err error) {
+ opts := rest.Opts{
+ Method: "POST",
+ Path: urlPathEscape(path),
+ Parameters: url.Values{},
+ }
+
+ opts.Parameters.Set("type", "WORKSTATION")
+
+ _, err = srv.CallXML(ctx, &opts, nil, &info)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't create device: %w", err)
+ }
+ return info, nil
+}
+
+// createMountPoint makes a mount point
+func createMountPoint(ctx context.Context, srv *rest.Client, path string) (info *api.JottaMountPoint, err error) {
+ opts := rest.Opts{
+ Method: "POST",
+ Path: urlPathEscape(path),
+ }
+
+ _, err = srv.CallXML(ctx, &opts, nil, &info)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't create mountpoint: %w", err)
+ }
+ return info, nil
+}
+
// setEndpointURL generates the API endpoint URL
func (f *Fs) setEndpointURL() {
if f.opt.Device == "" {
@@ -597,6 +721,7 @@ func (f *Fs) setEndpointURL() {
f.opt.Mountpoint = defaultMountpoint
}
f.endpointURL = path.Join(f.user, f.opt.Device, f.opt.Mountpoint)
+ f.allocateURL = path.Join("/jfs", f.opt.Device, f.opt.Mountpoint)
}
// readMetaDataForPath reads the metadata from the path
@@ -662,6 +787,11 @@ func (f *Fs) filePath(file string) string {
return urlPathEscape(f.filePathRaw(file))
}
+// allocatePathRaw returns an unescaped file path (f.root, file)
+func (f *Fs) allocatePathRaw(file string) string {
+ return path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
+}
+
// Jottacloud requires the grant_type 'refresh_token' string
// to be uppercase and throws a 400 Bad Request if we use the
// lower case used by the oauth2 module
@@ -1101,9 +1231,6 @@ func (f *Fs) createObject(remote string, modTime time.Time, size int64) (o *Obje
//
// The new object may have been created if an error is returned
func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
- if f.opt.Device != "Jotta" {
- return nil, errors.New("upload not supported for devices other than Jotta")
- }
o := f.createObject(src.Remote(), src.ModTime(ctx), src.Size())
return o, o.Update(ctx, in, src, options...)
}
@@ -1738,7 +1865,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
Created: fileDate,
Modified: fileDate,
Md5: md5String,
- Path: path.Join(o.fs.opt.Mountpoint, o.fs.opt.Enc.FromStandardPath(path.Join(o.fs.root, o.remote))),
+ Path: path.Join(o.fs.allocateURL, o.fs.opt.Enc.FromStandardPath(path.Join(o.fs.root, o.remote))),
}
// send it
diff --git a/docs/content/jottacloud.md b/docs/content/jottacloud.md
index 77afba24ac8f4..ef7ea558627e7 100644
--- a/docs/content/jottacloud.md
+++ b/docs/content/jottacloud.md
@@ -75,60 +75,83 @@ s) Set configuration password
q) Quit config
n/s/q> n
name> remote
+Option Storage.
Type of storage to configure.
-Enter a string value. Press Enter for the default ("").
-Choose a number from below, or type in your own value
+Choose a number from below, or type in your own value.
[snip]
XX / Jottacloud
- \ "jottacloud"
+ \ (jottacloud)
[snip]
Storage> jottacloud
-** See help for jottacloud backend at: https://rclone.org/jottacloud/ **
-
-Edit advanced config? (y/n)
-y) Yes
-n) No
-y/n> n
-Remote config
-Use legacy authentication?.
-This is only required for certain whitelabel versions of Jottacloud and not recommended for normal users.
+Edit advanced config?
y) Yes
n) No (default)
y/n> n
-
-Generate a personal login token here: https://www.jottacloud.com/web/secure
+Option config_type.
+Select authentication type.
+Choose a number from below, or type in an existing string value.
+Press Enter for the default (standard).
+ / Standard authentication.
+ 1 | Use this if you're a normal Jottacloud user.
+ \ (standard)
+ / Legacy authentication.
+ 2 | This is only required for certain whitelabel versions of Jottacloud and not recommended for normal users.
+ \ (legacy)
+ / Telia Cloud authentication.
+ 3 | Use this if you are using Telia Cloud.
+ \ (telia)
+ / Tele2 Cloud authentication.
+ 4 | Use this if you are using Tele2 Cloud.
+ \ (tele2)
+config_type> 1
+Personal login token.
+Generate here: https://www.jottacloud.com/web/secure
Login Token>
-
-Do you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?
-
+Use a non-standard device/mountpoint?
+Choosing no, the default, will let you access the storage used for the archive
+section of the official Jottacloud client. If you instead want to access the
+sync or the backup section, for example, you must choose yes.
y) Yes
-n) No
+n) No (default)
y/n> y
-Please select the device to use. Normally this will be Jotta
-Choose a number from below, or type in an existing value
+Option config_device.
+The device to use. In standard setup the built-in Jotta device is used,
+which contains predefined mountpoints for archive, sync etc. All other devices
+are treated as backup devices by the official Jottacloud client. You may create
+a new by entering a unique name.
+Choose a number from below, or type in your own string value.
+Press Enter for the default (DESKTOP-3H31129).
1 > DESKTOP-3H31129
2 > Jotta
-Devices> 2
-Please select the mountpoint to user. Normally this will be Archive
-Choose a number from below, or type in an existing value
+config_device> 2
+Option config_mountpoint.
+The mountpoint to use for the built-in device Jotta.
+The standard setup is to use the Archive mountpoint. Most other mountpoints
+have very limited support in rclone and should generally be avoided.
+Choose a number from below, or type in an existing string value.
+Press Enter for the default (Archive).
1 > Archive
- 2 > Links
+ 2 > Shared
3 > Sync
-
-Mountpoints> 1
+config_mountpoint> 1
--------------------
-[jotta]
+[remote]
type = jottacloud
+configVersion = 1
+client_id = jottacli
+client_secret =
+tokenURL = https://id.jottacloud.com/auth/realms/jottacloud/protocol/openid-connect/token
token = {........}
+username = 2940e57271a93d987d6f8a21
device = Jotta
mountpoint = Archive
-configVersion = 1
--------------------
-y) Yes this is OK
+y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> y
```
+
Once configured you can then use `rclone` like this,
List directories in top level of your Jottacloud
@@ -145,19 +168,27 @@ To copy a local directory to an Jottacloud directory called backup
### Devices and Mountpoints
-The official Jottacloud client registers a device for each computer you install it on,
-and then creates a mountpoint for each folder you select for Backup.
-The web interface uses a special device called Jotta for the Archive and Sync mountpoints.
-
-With rclone you'll want to use the Jotta/Archive device/mountpoint in most cases, however if you
-want to access files uploaded by any of the official clients rclone provides the option to select
-other devices and mountpoints during config. Note that uploading files is currently not supported
-to other devices than Jotta.
-
-The built-in Jotta device may also contain several other mountpoints, such as: Latest, Links, Shared and Trash.
-These are special mountpoints with a different internal representation than the "regular" mountpoints.
-Rclone will only to a very limited degree support them. Generally you should avoid these, unless you know what you
-are doing.
+The official Jottacloud client registers a device for each computer you install
+it on, and shows them in the backup section of the user interface. For each
+folder you select for backup it will create a mountpoint within this device.
+A built-in device called Jotta is special, and contains mountpoints Archive,
+Sync and some others, used for corresponding features in official clients.
+
+With rclone you'll want to use the standard Jotta/Archive device/mountpoint in
+most cases. However, you may for example want to access files from the sync or
+backup functionality provided by the official clients, and rclone therefore
+provides the option to select other devices and mountpoints during config.
+
+You are allowed to create new devices and mountpoints. All devices except the
+built-in Jotta device are treated as backup devices by official Jottacloud
+clients, and the mountpoints on them are individual backup sets.
+
+With the built-in Jotta device, only existing, built-in, mountpoints can be
+selected. In addition to the mentioned Archive and Sync, it may contain
+several other mountpoints such as: Latest, Links, Shared and Trash. All of
+these are special mountpoints with a different internal representation than
+the "regular" mountpoints. Rclone will only to a very limited degree support
+them. Generally you should avoid these, unless you know what you are doing.
### --fast-list
From a571c1fb46f531fb88409129f31c245c5f9274d3 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Tue, 18 Jan 2022 21:06:22 +0100
Subject: [PATCH 012/560] jottacloud: refactor naming of different api urls
---
backend/jottacloud/jottacloud.go | 54 ++++++++++++++++----------------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index 65df99f585915..cde7fb0760287 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -46,9 +46,9 @@ const (
decayConstant = 2 // bigger for slower decay, exponential
defaultDevice = "Jotta"
defaultMountpoint = "Archive"
- rootURL = "https://jfs.jottacloud.com/jfs/"
+ jfsURL = "https://jfs.jottacloud.com/jfs/"
apiURL = "https://api.jottacloud.com/"
- baseURL = "https://www.jottacloud.com/"
+ wwwURL = "https://www.jottacloud.com/"
cachePrefix = "rclone-jcmd5-"
configDevice = "device"
configMountpoint = "mountpoint"
@@ -277,7 +277,7 @@ sync or the backup section, for example, you must choose yes.`)
if err != nil {
return nil, err
}
- srv := rest.NewClient(oAuthClient).SetRoot(rootURL)
+ jfsSrv := rest.NewClient(oAuthClient).SetRoot(jfsURL)
apiSrv := rest.NewClient(oAuthClient).SetRoot(apiURL)
cust, err := getCustomerInfo(ctx, apiSrv)
@@ -286,7 +286,7 @@ sync or the backup section, for example, you must choose yes.`)
}
m.Set(configUsername, cust.Username)
- acc, err := getDriveInfo(ctx, srv, cust.Username)
+ acc, err := getDriveInfo(ctx, jfsSrv, cust.Username)
if err != nil {
return nil, err
}
@@ -316,11 +316,11 @@ a new by entering a unique name.`, defaultDevice)
if err != nil {
return nil, err
}
- srv := rest.NewClient(oAuthClient).SetRoot(rootURL)
+ jfsSrv := rest.NewClient(oAuthClient).SetRoot(jfsURL)
username, _ := m.Get(configUsername)
- acc, err := getDriveInfo(ctx, srv, username)
+ acc, err := getDriveInfo(ctx, jfsSrv, username)
if err != nil {
return nil, err
}
@@ -335,7 +335,7 @@ a new by entering a unique name.`, defaultDevice)
var dev *api.JottaDevice
if isNew {
fs.Debugf(nil, "Creating new device: %s", device)
- dev, err = createDevice(ctx, srv, path.Join(username, device))
+ dev, err = createDevice(ctx, jfsSrv, path.Join(username, device))
if err != nil {
return nil, err
}
@@ -343,7 +343,7 @@ a new by entering a unique name.`, defaultDevice)
m.Set(configDevice, device)
if !isNew {
- dev, err = getDeviceInfo(ctx, srv, path.Join(username, device))
+ dev, err = getDeviceInfo(ctx, jfsSrv, path.Join(username, device))
if err != nil {
return nil, err
}
@@ -372,12 +372,12 @@ You may create a new by entering a unique name.`, device)
if err != nil {
return nil, err
}
- srv := rest.NewClient(oAuthClient).SetRoot(rootURL)
+ jfsSrv := rest.NewClient(oAuthClient).SetRoot(jfsURL)
username, _ := m.Get(configUsername)
device, _ := m.Get(configDevice)
- dev, err := getDeviceInfo(ctx, srv, path.Join(username, device))
+ dev, err := getDeviceInfo(ctx, jfsSrv, path.Join(username, device))
if err != nil {
return nil, err
}
@@ -395,7 +395,7 @@ You may create a new by entering a unique name.`, device)
return nil, fmt.Errorf("custom mountpoints not supported on built-in %s device: %w", defaultDevice, err)
}
fs.Debugf(nil, "Creating new mountpoint: %s", mountpoint)
- _, err := createMountPoint(ctx, srv, path.Join(username, device, mountpoint))
+ _, err := createMountPoint(ctx, jfsSrv, path.Join(username, device, mountpoint))
if err != nil {
return nil, err
}
@@ -431,7 +431,7 @@ type Fs struct {
features *fs.Features
endpointURL string
allocateURL string
- srv *rest.Client
+ jfsSrv *rest.Client
apiSrv *rest.Client
pacer *fs.Pacer
tokenRenewer *oauthutil.Renew // renew the token on expiry
@@ -733,7 +733,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.Jo
var result api.JottaFile
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.CallXML(ctx, &opts, nil, &result)
+ resp, err = f.jfsSrv.CallXML(ctx, &opts, nil, &result)
return shouldRetry(ctx, resp, err)
})
@@ -899,7 +899,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
name: name,
root: root,
opt: *opt,
- srv: rest.NewClient(oAuthClient).SetRoot(rootURL),
+ jfsSrv: rest.NewClient(oAuthClient).SetRoot(jfsURL),
apiSrv: rest.NewClient(oAuthClient).SetRoot(apiURL),
pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
}
@@ -909,7 +909,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
ReadMimeType: true,
WriteMimeType: false,
}).Fill(ctx, f)
- f.srv.SetErrorHandler(errorHandler)
+ f.jfsSrv.SetErrorHandler(errorHandler)
if opt.TrashedOnly { // we cannot support showing Trashed Files when using ListR right now
f.features.ListR = nil
}
@@ -994,7 +994,7 @@ func (f *Fs) CreateDir(ctx context.Context, path string) (jf *api.JottaFolder, e
opts.Parameters.Set("mkDir", "true")
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.CallXML(ctx, &opts, nil, &jf)
+ resp, err = f.jfsSrv.CallXML(ctx, &opts, nil, &jf)
return shouldRetry(ctx, resp, err)
})
if err != nil {
@@ -1023,7 +1023,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
var resp *http.Response
var result api.JottaFolder
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.CallXML(ctx, &opts, nil, &result)
+ resp, err = f.jfsSrv.CallXML(ctx, &opts, nil, &result)
return shouldRetry(ctx, resp, err)
})
@@ -1181,7 +1181,7 @@ func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.Call(ctx, &opts)
+ resp, err = f.jfsSrv.Call(ctx, &opts)
if err != nil {
return shouldRetry(ctx, resp, err)
}
@@ -1291,7 +1291,7 @@ func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) (err error)
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.Call(ctx, &opts)
+ resp, err = f.jfsSrv.Call(ctx, &opts)
return shouldRetry(ctx, resp, err)
})
if err != nil {
@@ -1344,7 +1344,7 @@ func (f *Fs) createOrUpdate(ctx context.Context, file string, modTime time.Time,
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.CallXML(ctx, &opts, nil, &info)
+ resp, err = f.jfsSrv.CallXML(ctx, &opts, nil, &info)
return shouldRetry(ctx, resp, err)
})
@@ -1369,7 +1369,7 @@ func (f *Fs) copyOrMove(ctx context.Context, method, src, dest string) (info *ap
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.CallXML(ctx, &opts, nil, &info)
+ resp, err = f.jfsSrv.CallXML(ctx, &opts, nil, &info)
return shouldRetry(ctx, resp, err)
})
if err != nil {
@@ -1503,7 +1503,7 @@ func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration,
var resp *http.Response
var result api.JottaFile
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.CallXML(ctx, &opts, nil, &result)
+ resp, err = f.jfsSrv.CallXML(ctx, &opts, nil, &result)
return shouldRetry(ctx, resp, err)
})
@@ -1529,19 +1529,19 @@ func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration,
return "", errors.New("couldn't create public link - no uri received")
}
if result.PublicSharePath != "" {
- webLink := joinPath(baseURL, result.PublicSharePath)
+ webLink := joinPath(wwwURL, result.PublicSharePath)
fs.Debugf(nil, "Web link: %s", webLink)
} else {
fs.Debugf(nil, "No web link received")
}
- directLink := joinPath(baseURL, fmt.Sprintf("opin/io/downloadPublic/%s/%s", f.user, result.PublicURI))
+ directLink := joinPath(wwwURL, fmt.Sprintf("opin/io/downloadPublic/%s/%s", f.user, result.PublicURI))
fs.Debugf(nil, "Direct link: %s", directLink)
return directLink, nil
}
// About gets quota information
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
- info, err := getDriveInfo(ctx, f.srv, f.user)
+ info, err := getDriveInfo(ctx, f.jfsSrv, f.user)
if err != nil {
return nil, err
}
@@ -1744,7 +1744,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
opts.Parameters.Set("mode", "bin")
err = o.fs.pacer.Call(func() (bool, error) {
- resp, err = o.fs.srv.Call(ctx, &opts)
+ resp, err = o.fs.jfsSrv.Call(ctx, &opts)
return shouldRetry(ctx, resp, err)
})
if err != nil {
@@ -1935,7 +1935,7 @@ func (o *Object) remove(ctx context.Context, hard bool) error {
}
return o.fs.pacer.Call(func() (bool, error) {
- resp, err := o.fs.srv.CallXML(ctx, &opts, nil, nil)
+ resp, err := o.fs.jfsSrv.CallXML(ctx, &opts, nil, nil)
return shouldRetry(ctx, resp, err)
})
}
From b2388f129442f4e610d0508e01f8e66a3b337a60 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Tue, 18 Jan 2022 21:48:44 +0100
Subject: [PATCH 013/560] jottacloud: refactor endpoint paths
---
backend/jottacloud/jottacloud.go | 62 +++++++++++++++++++-------------
1 file changed, 37 insertions(+), 25 deletions(-)
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index cde7fb0760287..1271eda1af474 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -424,17 +424,17 @@ type Options struct {
// Fs represents a remote jottacloud
type Fs struct {
- name string
- root string
- user string
- opt Options
- features *fs.Features
- endpointURL string
- allocateURL string
- jfsSrv *rest.Client
- apiSrv *rest.Client
- pacer *fs.Pacer
- tokenRenewer *oauthutil.Renew // renew the token on expiry
+ name string
+ root string
+ user string
+ opt Options
+ features *fs.Features
+ fileEndpoint string
+ allocateEndpoint string
+ jfsSrv *rest.Client
+ apiSrv *rest.Client
+ pacer *fs.Pacer
+ tokenRenewer *oauthutil.Renew // renew the token on expiry
}
// Object describes a jottacloud object
@@ -712,16 +712,16 @@ func createMountPoint(ctx context.Context, srv *rest.Client, path string) (info
return info, nil
}
-// setEndpointURL generates the API endpoint URL
-func (f *Fs) setEndpointURL() {
+// setEndpoints generates the API endpoints
+func (f *Fs) setEndpoints() {
if f.opt.Device == "" {
f.opt.Device = defaultDevice
}
if f.opt.Mountpoint == "" {
f.opt.Mountpoint = defaultMountpoint
}
- f.endpointURL = path.Join(f.user, f.opt.Device, f.opt.Mountpoint)
- f.allocateURL = path.Join("/jfs", f.opt.Device, f.opt.Mountpoint)
+ f.fileEndpoint = path.Join(f.user, f.opt.Device, f.opt.Mountpoint)
+ f.allocateEndpoint = path.Join("/jfs", f.opt.Device, f.opt.Mountpoint)
}
// readMetaDataForPath reads the metadata from the path
@@ -778,18 +778,30 @@ func urlPathEscape(in string) string {
}
// filePathRaw returns an unescaped file path (f.root, file)
-func (f *Fs) filePathRaw(file string) string {
- return path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
+// Optionally made absolute by prefixing with "/", typically required when used
+// as request parameter instead of the path (which is relative to some root url).
+func (f *Fs) filePathRaw(file string, absolute bool) string {
+ prefix := ""
+ if absolute {
+ prefix = "/"
+ }
+ return path.Join(prefix, f.fileEndpoint, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
}
// filePath returns an escaped file path (f.root, file)
func (f *Fs) filePath(file string) string {
- return urlPathEscape(f.filePathRaw(file))
+ return urlPathEscape(f.filePathRaw(file, false))
}
-// allocatePathRaw returns an unescaped file path (f.root, file)
-func (f *Fs) allocatePathRaw(file string) string {
- return path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
+// allocatePathRaw returns an unescaped allocate file path (f.root, file)
+// Optionally made absolute by prefixing with "/", typically required when used
+// as request parameter instead of the path (which is relative to some root url).
+func (f *Fs) allocatePathRaw(file string, absolute bool) string {
+ prefix := ""
+ if absolute {
+ prefix = "/"
+ }
+ return path.Join(prefix, f.allocateEndpoint, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
}
// Jottacloud requires the grant_type 'refresh_token' string
@@ -928,7 +940,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
return nil, err
}
f.user = cust.Username
- f.setEndpointURL()
+ f.setEndpoints()
if root != "" && !rootIsDir {
// Check to see if the root actually an existing file
@@ -1365,7 +1377,7 @@ func (f *Fs) copyOrMove(ctx context.Context, method, src, dest string) (info *ap
Parameters: url.Values{},
}
- opts.Parameters.Set(method, "/"+path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, dest))))
+ opts.Parameters.Set(method, f.filePathRaw(dest, true))
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
@@ -1478,7 +1490,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
return fs.ErrorDirExists
}
- _, err = f.copyOrMove(ctx, "mvDir", path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(srcPath))+"/", dstRemote)
+ _, err = f.copyOrMove(ctx, "mvDir", path.Join(f.fileEndpoint, f.opt.Enc.FromStandardPath(srcPath))+"/", dstRemote)
if err != nil {
return fmt.Errorf("couldn't move directory: %w", err)
@@ -1865,7 +1877,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
Created: fileDate,
Modified: fileDate,
Md5: md5String,
- Path: path.Join(o.fs.allocateURL, o.fs.opt.Enc.FromStandardPath(path.Join(o.fs.root, o.remote))),
+ Path: o.fs.allocatePathRaw(o.remote, true),
}
// send it
From e3d44612c1cf68d920cb090cd8d2f2380bee12f2 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 19 Jan 2022 10:26:05 +0100
Subject: [PATCH 014/560] jottacloud: error strings should not be capitalized
---
backend/jottacloud/jottacloud.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index 1271eda1af474..12fe1d255cb6d 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -834,12 +834,12 @@ func getOAuthClient(ctx context.Context, name string, m configmap.Mapper) (oAuth
if ok {
ver, err = strconv.Atoi(version)
if err != nil {
- return nil, nil, errors.New("Failed to parse config version")
+ return nil, nil, errors.New("failed to parse config version")
}
ok = (ver == configVersion) || (ver == legacyConfigVersion)
}
if !ok {
- return nil, nil, errors.New("Outdated config - please reconfigure this backend")
+ return nil, nil, errors.New("outdated config - please reconfigure this backend")
}
baseClient := fshttp.NewClient(ctx)
@@ -885,7 +885,7 @@ func getOAuthClient(ctx context.Context, name string, m configmap.Mapper) (oAuth
// Create OAuth Client
oAuthClient, ts, err = oauthutil.NewClientWithBaseClient(ctx, name, m, oauthConfig, baseClient)
if err != nil {
- return nil, nil, fmt.Errorf("Failed to configure Jottacloud oauth client: %w", err)
+ return nil, nil, fmt.Errorf("failed to configure Jottacloud oauth client: %w", err)
}
return oAuthClient, ts, nil
}
@@ -1172,7 +1172,7 @@ func parseListRStream(ctx context.Context, r io.Reader, filesystem *Fs, callback
if expected.Folders != actual.Folders ||
expected.Files != actual.Files {
- return fmt.Errorf("Invalid result from listStream: expected[%#v] != actual[%#v]", expected, actual)
+ return fmt.Errorf("invalid result from listStream: expected[%#v] != actual[%#v]", expected, actual)
}
return nil
}
From ef089dd867e15e257efa7d635421c615a4dd15ea Mon Sep 17 00:00:00 2001
From: Noah Hsu
Date: Thu, 9 Jun 2022 00:24:35 +0800
Subject: [PATCH 015/560] webdav: add SharePoint in other specific regions
support
---
backend/webdav/odrvcookie/fetch.go | 36 +++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/backend/webdav/odrvcookie/fetch.go b/backend/webdav/odrvcookie/fetch.go
index f6b25d29eeedc..c3ade04e5d5a3 100644
--- a/backend/webdav/odrvcookie/fetch.go
+++ b/backend/webdav/odrvcookie/fetch.go
@@ -74,7 +74,7 @@ xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-util
http://www.w3.org/2005/08/addressing/anonymous
-https://login.microsoftonline.com/extSTS.srf
+{{ .SPTokenURL }}
@@ -159,11 +159,37 @@ func (ca *CookieAuth) getSPCookie(conf *SharepointSuccessResponse) (*CookieRespo
return &cookieResponse, nil
}
+var spTokenURLMap = map[string]string{
+ "com": "https://login.microsoftonline.com",
+ "cn": "https://login.chinacloudapi.cn",
+ "us": "https://login.microsoftonline.us",
+ "de": "https://login.microsoftonline.de",
+}
+
+func getSPTokenURL(endpoint string) (string, error) {
+ spRoot, err := url.Parse(endpoint)
+ if err != nil {
+ return "", fmt.Errorf("error while parse endpoint: %w", err)
+ }
+ domains := strings.Split(spRoot.Host, ".")
+ tld := domains[len(domains)-1]
+ spTokenURL, ok := spTokenURLMap[tld]
+ if !ok {
+ return "", fmt.Errorf("error while get SPToken url, unsupported tld: %s", tld)
+ }
+ return spTokenURL + "/extSTS.srf", nil
+}
+
func (ca *CookieAuth) getSPToken(ctx context.Context) (conf *SharepointSuccessResponse, err error) {
+ spTokenURL, err := getSPTokenURL(ca.endpoint)
+ if err != nil {
+ return nil, err
+ }
reqData := map[string]interface{}{
- "Username": ca.user,
- "Password": ca.pass,
- "Address": ca.endpoint,
+ "Username": ca.user,
+ "Password": ca.pass,
+ "Address": ca.endpoint,
+ "SPTokenURL": spTokenURL,
}
t := template.Must(template.New("authXML").Parse(reqString))
@@ -175,7 +201,7 @@ func (ca *CookieAuth) getSPToken(ctx context.Context) (conf *SharepointSuccessRe
// Create and execute the first request which returns an auth token for the sharepoint service
// With this token we can authenticate on the login page and save the returned cookies
- req, err := http.NewRequestWithContext(ctx, "POST", "https://login.microsoftonline.com/extSTS.srf", buf)
+ req, err := http.NewRequestWithContext(ctx, "POST", spTokenURL, buf)
if err != nil {
return nil, err
}
From 61a75bfe07c565fbbaa4f68f33a014cb6babae0b Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sat, 28 May 2022 13:52:48 +0200
Subject: [PATCH 016/560] config: add empty line between sections to improve
readability
See #6211
---
fs/config/ui.go | 33 ++++++++++++++++++++++++++++-----
1 file changed, 28 insertions(+), 5 deletions(-)
diff --git a/fs/config/ui.go b/fs/config/ui.go
index 0e54394b13f1e..6dbe0c5d3fad0 100644
--- a/fs/config/ui.go
+++ b/fs/config/ui.go
@@ -338,6 +338,11 @@ func OkRemote(name string) bool {
return false
}
+// newSection prints an empty line to separate sections
+func newSection() {
+ fmt.Println()
+}
+
// backendConfig configures the backend starting from the state passed in
//
// The is the user interface loop that drives the post configuration backend config.
@@ -387,6 +392,7 @@ func backendConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.
if out.State == "" {
break
}
+ newSection()
}
return nil
}
@@ -516,7 +522,7 @@ func NewRemote(ctx context.Context, name string) error {
break
}
LoadedData().SetValue(name, "type", newType)
-
+ newSection()
_, err = CreateRemote(ctx, name, newType, nil, UpdateRemoteOpt{
All: true,
})
@@ -527,13 +533,15 @@ func NewRemote(ctx context.Context, name string) error {
SaveConfig()
return nil
}
+ newSection()
return EditRemote(ctx, ri, name)
}
// EditRemote gets the user to edit a remote
func EditRemote(ctx context.Context, ri *fs.RegInfo, name string) error {
- ShowRemote(name)
fmt.Printf("Edit remote\n")
+ ShowRemote(name)
+ newSection()
for {
_, err := UpdateRemote(ctx, name, nil, UpdateRemoteOpt{
All: true,
@@ -626,29 +634,44 @@ func EditConfig(ctx context.Context) (err error) {
}
switch i := Command(what); i {
case 'e':
+ newSection()
name := ChooseRemote()
+ newSection()
fs := mustFindByName(name)
err = EditRemote(ctx, fs, name)
if err != nil {
return err
}
case 'n':
- err = NewRemote(ctx, NewRemoteName())
+ newSection()
+ name := NewRemoteName()
+ newSection()
+ err = NewRemote(ctx, name)
if err != nil {
return err
}
case 'd':
+ newSection()
name := ChooseRemote()
+ newSection()
DeleteRemote(name)
case 'r':
- RenameRemote(ChooseRemote())
+ newSection()
+ name := ChooseRemote()
+ newSection()
+ RenameRemote(name)
case 'c':
- CopyRemote(ChooseRemote())
+ newSection()
+ name := ChooseRemote()
+ newSection()
+ CopyRemote(name)
case 's':
+ newSection()
SetPassword()
case 'q':
return nil
}
+ newSection()
}
}
From b246584a02df46afece9b7f5d55df83fc9a31a86 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sat, 28 May 2022 15:53:47 +0200
Subject: [PATCH 017/560] config: more readable listing of remote options
Differentiate output of 'config show remote' command from listing options as part
of interactive config process for consistency: 'config show remote' consistent with
'config show', while listing in interactive config consistent with other output.
See #6211
---
fs/config/ui.go | 29 +++++++++++++++++++----------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/fs/config/ui.go b/fs/config/ui.go
index 6dbe0c5d3fad0..73b2925b26a17 100644
--- a/fs/config/ui.go
+++ b/fs/config/ui.go
@@ -298,10 +298,8 @@ func mustFindByName(name string) *fs.RegInfo {
return fs.MustFind(fsType)
}
-// ShowRemote shows the contents of the remote
-func ShowRemote(name string) {
- fmt.Printf("--------------------\n")
- fmt.Printf("[%s]\n", name)
+// printRemoteOptions prints the options of the remote
+func printRemoteOptions(name string, prefix string, sep string) {
fs := mustFindByName(name)
for _, key := range LoadedData().GetKeyList(name) {
isPassword := false
@@ -313,17 +311,28 @@ func ShowRemote(name string) {
}
value := FileGet(name, key)
if isPassword && value != "" {
- fmt.Printf("%s = *** ENCRYPTED ***\n", key)
+ fmt.Printf("%s%s%s*** ENCRYPTED ***\n", prefix, key, sep)
} else {
- fmt.Printf("%s = %s\n", key, value)
+ fmt.Printf("%s%s%s%s\n", prefix, key, sep, value)
}
}
- fmt.Printf("--------------------\n")
+}
+
+// listRemoteOptions lists the options of the remote
+func listRemoteOptions(name string) {
+ printRemoteOptions(name, "- ", ": ")
+}
+
+// ShowRemote shows the contents of the remote in config file format
+func ShowRemote(name string) {
+ fmt.Printf("[%s]\n", name)
+ printRemoteOptions(name, "", " = ")
}
// OkRemote prints the contents of the remote and ask if it is OK
func OkRemote(name string) bool {
- ShowRemote(name)
+ fmt.Printf("Configuration of %q remote:\n", name)
+ listRemoteOptions(name)
switch i := CommandDefault([]string{"yYes this is OK", "eEdit this remote", "dDelete this remote"}, 0); i {
case 'y':
return true
@@ -539,8 +548,8 @@ func NewRemote(ctx context.Context, name string) error {
// EditRemote gets the user to edit a remote
func EditRemote(ctx context.Context, ri *fs.RegInfo, name string) error {
- fmt.Printf("Edit remote\n")
- ShowRemote(name)
+ fmt.Printf("Edit existing %q remote with options:\n", name)
+ listRemoteOptions(name)
newSection()
for {
_, err := UpdateRemote(ctx, name, nil, UpdateRemoteOpt{
From acd7ad9190cc11792dc7beeeb56d9f1aa31385ab Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 1 Jun 2022 21:50:20 +0200
Subject: [PATCH 018/560] config: ajust section headers to improve readability
See #6211
---
fs/config/ui.go | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/fs/config/ui.go b/fs/config/ui.go
index 73b2925b26a17..60b5b3bc61e1e 100644
--- a/fs/config/ui.go
+++ b/fs/config/ui.go
@@ -285,6 +285,7 @@ func ShowRemotes() {
func ChooseRemote() string {
remotes := LoadedData().GetSectionList()
sort.Strings(remotes)
+ fmt.Println("Select remote.")
return Choose("remote", "value", remotes, nil, "", true, false)
}
@@ -331,8 +332,10 @@ func ShowRemote(name string) {
// OkRemote prints the contents of the remote and ask if it is OK
func OkRemote(name string) bool {
- fmt.Printf("Configuration of %q remote:\n", name)
+ fmt.Println("Configuration complete.")
+ fmt.Println("Options:")
listRemoteOptions(name)
+ fmt.Printf("Keep this %q remote?\n", name)
switch i := CommandDefault([]string{"yYes this is OK", "eEdit this remote", "dDelete this remote"}, 0); i {
case 'y':
return true
@@ -492,6 +495,7 @@ func ChooseOption(o *fs.Option, name string) string {
// NewRemoteName asks the user for a name for a new remote
func NewRemoteName() (name string) {
for {
+ fmt.Println("Enter name for new remote.")
fmt.Printf("name> ")
name = ReadLine()
if LoadedData().HasSection(name) {
@@ -548,7 +552,7 @@ func NewRemote(ctx context.Context, name string) error {
// EditRemote gets the user to edit a remote
func EditRemote(ctx context.Context, ri *fs.RegInfo, name string) error {
- fmt.Printf("Edit existing %q remote with options:\n", name)
+ fmt.Printf("Editing existing %q remote with options:\n", name)
listRemoteOptions(name)
newSection()
for {
From 08a897424bc8c2fe27a5d8c9477b3c2043708c5a Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 18:29:28 +0100
Subject: [PATCH 019/560] Add Noah Hsu to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 23f53103628a0..8d5cbbac44fbe 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -605,3 +605,4 @@ put them back in again.` >}}
* Nick
* Jason Zheng
* Matthew Vernon
+ * Noah Hsu
From 6c2331ffd73096c3957844bd054fc94cdf2654e8 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 18:01:06 +0100
Subject: [PATCH 020/560] mount: skip tests on CI even if >= 2 processors
---
cmd/mount/mount_test.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/cmd/mount/mount_test.go b/cmd/mount/mount_test.go
index 2c1e027f1ce06..64f53eadcb841 100644
--- a/cmd/mount/mount_test.go
+++ b/cmd/mount/mount_test.go
@@ -7,6 +7,7 @@ import (
"runtime"
"testing"
+ "github.com/rclone/rclone/fstest/testy"
"github.com/rclone/rclone/vfs/vfstest"
)
@@ -14,5 +15,6 @@ func TestMount(t *testing.T) {
if runtime.NumCPU() <= 2 {
t.Skip("FIXME skipping mount tests as they lock up on <= 2 CPUs - See: https://github.com/rclone/rclone/issues/3154")
}
+ testy.SkipUnreliable(t)
vfstest.RunTests(t, false, mount)
}
From bb6edb3c39c31e194dfc4511057ebf6741fef68e Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 17:44:55 +0100
Subject: [PATCH 021/560] build: update dependencies
Also:
- azureblob: fix compile after API change in upstream library
---
backend/azureblob/azureblob.go | 2 +-
go.mod | 35 +++++-----
go.sum | 122 +++++++++++++++++++++------------
3 files changed, 97 insertions(+), 62 deletions(-)
diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go
index f94c1ea00f1bf..58f4552b9bd4f 100644
--- a/backend/azureblob/azureblob.go
+++ b/backend/azureblob/azureblob.go
@@ -1763,7 +1763,7 @@ func (o *Object) SetTier(tier string) error {
blob := o.getBlobReference()
ctx := context.Background()
err := o.fs.pacer.Call(func() (bool, error) {
- _, err := blob.SetTier(ctx, desiredAccessTier, azblob.LeaseAccessConditions{})
+ _, err := blob.SetTier(ctx, desiredAccessTier, azblob.LeaseAccessConditions{}, azblob.RehydratePriorityStandard)
return o.fs.shouldRetry(ctx, err)
})
diff --git a/go.mod b/go.mod
index f9364e3219928..75a242ff7a701 100644
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,8 @@ go 1.16
require (
bazil.org/fuse v0.0.0-20200524192727-fb710f7dfd05
github.com/Azure/azure-pipeline-go v0.2.3
- github.com/Azure/azure-storage-blob-go v0.14.0
- github.com/Azure/go-autorest/autorest/adal v0.9.18
+ github.com/Azure/azure-storage-blob-go v0.15.0
+ github.com/Azure/go-autorest/autorest/adal v0.9.20
github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e
github.com/Max-Sum/base32768 v0.0.0-20191205131208-7937843c71d5
github.com/Unknwon/goconfig v1.0.0
@@ -16,7 +16,7 @@ require (
github.com/anacrolix/dms v1.4.0
github.com/artyom/mtab v1.0.0
github.com/atotto/clipboard v0.1.4
- github.com/aws/aws-sdk-go v1.43.30
+ github.com/aws/aws-sdk-go v1.44.29
github.com/buengese/sgzip v0.1.1
github.com/colinmarc/hdfs/v2 v2.3.0
github.com/coreos/go-semver v0.3.0
@@ -30,7 +30,7 @@ require (
github.com/iguanesolutions/go-systemd/v5 v5.1.0
github.com/jcmturner/gokrb5/v8 v8.4.2
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004
- github.com/klauspost/compress v1.15.1
+ github.com/klauspost/compress v1.15.6
github.com/koofr/go-httpclient v0.0.0-20200420163713-93aa7c75b348
github.com/koofr/go-koofrclient v0.0.0-20190724113126-8e5366da203a
github.com/mattn/go-colorable v0.1.12
@@ -42,15 +42,15 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/sftp v1.13.5-0.20211228200725-31aac3e1878d
github.com/pmezard/go-difflib v1.0.0
- github.com/prometheus/client_golang v1.12.1
+ github.com/prometheus/client_golang v1.12.2
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8
github.com/rfjakob/eme v1.1.2
- github.com/shirou/gopsutil/v3 v3.22.3
+ github.com/shirou/gopsutil/v3 v3.22.5
github.com/sirupsen/logrus v1.8.1
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
- github.com/stretchr/testify v1.7.1
+ github.com/stretchr/testify v1.7.2
github.com/t3rm1n4l/go-mega v0.0.0-20200416171014-ffad7fcb44b8
github.com/winfsp/cgofuse v1.5.1-0.20220421173602-ce7e5a65cac7
github.com/xanzy/ssh-agent v0.3.1
@@ -58,16 +58,16 @@ require (
github.com/yunify/qingstor-sdk-go/v3 v3.2.0
go.etcd.io/bbolt v1.3.6
goftp.io/server v0.4.1
- golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
- golang.org/x/net v0.0.0-20220325170049-de3da57026de
- golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
- golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
- golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f
+ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
+ golang.org/x/net v0.0.0-20220607020251-c690dde0001d
+ golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
+ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
+ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/text v0.3.7
- golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
- google.golang.org/api v0.74.0
+ golang.org/x/time v0.0.0-20220411224347-583f2d630306
+ google.golang.org/api v0.83.0
gopkg.in/yaml.v2 v2.4.0
- storj.io/uplink v1.8.1
+ storj.io/uplink v1.9.0
)
require (
@@ -76,7 +76,6 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/jlaffaye/ftp v0.0.0-20220524001917-dfa1e758f3af
- golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13
- golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd
+ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
)
diff --git a/go.sum b/go.sum
index ff01c201275dc..8d64ccdfae170 100644
--- a/go.sum
+++ b/go.sum
@@ -40,8 +40,10 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
-cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@@ -61,13 +63,13 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
-github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM=
-github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
+github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
+github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
-github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg=
+github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
@@ -116,8 +118,8 @@ github.com/artyom/mtab v1.0.0 h1:r7OSVo5Jeqi8+LotZ0rT2kzfPIBp9KCpEJP8RQqGmSE=
github.com/artyom/mtab v1.0.0/go.mod h1:EHpkp5OmPfS1yZX+/DFTztlJ9di5UzdDLX1/XzWPXw8=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/aws/aws-sdk-go v1.43.30 h1:Q3lgrX/tz/MkEiPVVQnOQThBAK2QC2SCTCKTD1mwGFA=
-github.com/aws/aws-sdk-go v1.43.30/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go v1.44.29 h1:53YWlelsMiYmGxuTRpAq7Xp+pE+0esAVqNFiNyekU+A=
+github.com/aws/aws-sdk-go v1.44.29/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -147,6 +149,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/colinmarc/hdfs/v2 v2.3.0 h1:tMxOjXn6+7iPUlxAyup9Ha2hnmLe3Sv5DM2qqbSQ2VY=
github.com/colinmarc/hdfs/v2 v2.3.0/go.mod h1:nsyY1uyQOomU34KVQk9Qb/lDJobN1MQ/9WS6IqcVZno=
@@ -178,6 +181,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@@ -265,8 +269,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -304,8 +309,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
-github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -373,8 +380,8 @@ github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
-github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
+github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koofr/go-httpclient v0.0.0-20200420163713-93aa7c75b348 h1:Lrn8srO9JDBCf2iPjqy62stl49UDwoOxZ9/NGVi+fnk=
@@ -477,8 +484,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
-github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -515,8 +522,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/shirou/gopsutil/v3 v3.22.3 h1:UebRzEomgMpv61e3hgD1tGooqX5trFbdU/ehphbHd00=
-github.com/shirou/gopsutil/v3 v3.22.3/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM=
+github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
+github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
@@ -574,8 +581,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/t3rm1n4l/go-mega v0.0.0-20200416171014-ffad7fcb44b8 h1:IGJQmLBLYBdAknj21W3JsVof0yjEXfy1Q0K3YZebDOg=
github.com/t3rm1n4l/go-mega v0.0.0-20200416171014-ffad7fcb44b8/go.mod h1:XWL4vDyd3JKmJx+hZWUVgCNmmhZ2dTBcaNDcxH465s0=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
@@ -610,8 +618,9 @@ github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
github.com/zeebo/admission/v3 v3.0.3/go.mod h1:2OWyAS5yo0Xvj2AEUosOjTUHxaY0oIIiCrXGKCYzWpo=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
-github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
+github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
+github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo=
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54/go.mod h1:EI8LcOBDlSL3POyqwC1eJhOYlMBMidES+613EtmmT5w=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
@@ -644,13 +653,14 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -679,8 +689,8 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13 h1:C4eR3yV9J3RkP8WKkcQzpcPyr/foOcUtxW72DvJEOlI=
-golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
+golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd h1:x1GptNaTtxPAlTVIAJk61fuXg0y17h09DTxyb+VNC/k=
+golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -738,13 +748,17 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -764,8 +778,11 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -777,8 +794,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -821,7 +839,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -861,11 +878,14 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
-golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -880,8 +900,8 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
-golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -944,8 +964,10 @@ golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -983,8 +1005,11 @@ google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tD
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
-google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.83.0 h1:pMvST+6v+46Gabac4zlJlalxZjCeRcepwg2EdBU+nCc=
+google.golang.org/api v0.83.0/go.mod h1:CNywQoj/AfhTw26ZWAa6LwOv+6WFxHmeLPZq2uncLZk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1065,8 +1090,15 @@ google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb h1:0m9wktIpOxGw+SSKmydXWB3Z3GTfcPP6+q75HCQa6HI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 h1:qRu95HZ148xXw+XeZ3dvqe85PxH4X8+jIo0iRPKcEnM=
+google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@@ -1097,8 +1129,11 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1112,8 +1147,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1136,8 +1172,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1152,9 +1188,9 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
-storj.io/common v0.0.0-20220317162831-b0b4a044a95f h1:3Bl5reCTJF8Py4DLIj/drvLc91pCiTHOSMIfGAVtOXs=
-storj.io/common v0.0.0-20220317162831-b0b4a044a95f/go.mod h1:xW3PPPGBo4bdMtEP9GREnmxQptmJNuDg1tEHcA4zqog=
-storj.io/drpc v0.0.29 h1:Ihd4ls/JQFr0lctefie3iu+3QM4duccCKr9uMzf4sKY=
-storj.io/drpc v0.0.29/go.mod h1:6rcOyR/QQkSTX/9L5ZGtlZaE2PtXTTZl8d+ulSeeYEg=
-storj.io/uplink v1.8.1 h1:eu3lpnsgIyE7I9QrwqP1rsLUU8M+qSvUxSj5xjnKgOE=
-storj.io/uplink v1.8.1/go.mod h1:2lc2LprGbajoDCV5/QIOzAJiFwK1Lrw8m60kx6UrtT8=
+storj.io/common v0.0.0-20220414110316-a5cb7172d6bf h1:D5xZTDOlTTQWdAWeKKm2pFLcz1sceH+f/pVAcYB9jL8=
+storj.io/common v0.0.0-20220414110316-a5cb7172d6bf/go.mod h1:LBJrpAqL4MNSrhGEwc8SJ+tIVtgfCtFEZqDy6/0j67A=
+storj.io/drpc v0.0.30 h1:jqPe4T9KEu3CDBI05A2hCMgMSHLtd/E0N0yTF9QreIE=
+storj.io/drpc v0.0.30/go.mod h1:6rcOyR/QQkSTX/9L5ZGtlZaE2PtXTTZl8d+ulSeeYEg=
+storj.io/uplink v1.9.0 h1:Zg1kX1VqOQIKm0yAukteKpLuT68Be3euyNRML612ERM=
+storj.io/uplink v1.9.0/go.mod h1:f6D8306j5mnRHnPDKWCiwtPM6ukyGg77to9LaAY9l6k=
From 218bf2183de32d838bf20e987f2b24064625d2be Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 8 Jun 2022 20:47:58 +0200
Subject: [PATCH 022/560] docs: add missing backends from listing of optional
features (Akamai, Koofr and Sia)
---
docs/content/overview.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/docs/content/overview.md b/docs/content/overview.md
index b652ac7558a7b..c107723eab65c 100644
--- a/docs/content/overview.md
+++ b/docs/content/overview.md
@@ -446,8 +446,9 @@ upon backend-specific capabilities.
| Name | Purge | Copy | Move | DirMove | CleanUp | ListR | StreamUpload | LinkSharing | About | EmptyDir |
| ---------------------------- |:-----:|:----:|:----:|:-------:|:-------:|:-----:|:------------:|:------------:|:-----:|:--------:|
| 1Fichier | No | Yes | Yes | No | No | No | No | Yes | No | Yes |
+| Akamai Netstorage | Yes | No | No | No | No | Yes | Yes | No | No | Yes |
| Amazon Drive | Yes | No | Yes | Yes | No | No | No | No | No | Yes |
-| Amazon S3 | No | Yes | No | No | Yes | Yes | Yes | Yes | No | No |
+| Amazon S3 (or S3 compatible) | No | Yes | No | No | Yes | Yes | Yes | Yes | No | No |
| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | Yes | No | No |
| Box | Yes | Yes | Yes | Yes | Yes ‡‡ | No | Yes | Yes | Yes | Yes |
| Citrix ShareFile | Yes | Yes | Yes | Yes | No | No | Yes | No | No | Yes |
@@ -462,6 +463,7 @@ upon backend-specific capabilities.
| Hubic | Yes † | Yes | No | No | No | Yes | Yes | No | Yes | No |
| Internet Archive | No | Yes | No | No | Yes | Yes | No | Yes | Yes | No |
| Jottacloud | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes |
+| Koofr | Yes | Yes | Yes | Yes | No | No | Yes | Yes | Yes | Yes |
| Mail.ru Cloud | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | Yes |
| Mega | Yes | No | Yes | Yes | Yes | No | No | Yes | Yes | Yes |
| Memory | No | Yes | No | No | No | Yes | Yes | No | No | No |
@@ -475,6 +477,7 @@ upon backend-specific capabilities.
| QingStor | No | Yes | No | No | Yes | Yes | No | No | No | No |
| Seafile | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| SFTP | No | No | Yes | Yes | No | No | Yes | No | Yes | Yes |
+| Sia | No | No | No | No | No | No | Yes | No | No | Yes |
| SugarSync | Yes | Yes | Yes | Yes | No | No | Yes | Yes | No | Yes |
| Storj | Yes † | No | Yes | No | No | Yes | Yes | No | No | No |
| Uptobox | No | Yes | Yes | Yes | No | No | No | No | No | No |
From b4091f282aeeeeb96195cdd4e52b64ac738cdb15 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 27 Oct 2021 17:01:54 +0200
Subject: [PATCH 023/560] sftp: add support for about and hashsum on windows
server
Windows shells like cmd and powershell needs to use different quoting/escaping
of strings and paths than the unix shell, and also absolute paths must be fixed
by removing leading slash that the POSIX formatted paths have
(e.g. /C:/Users does not work in shell, it must be converted to C:/Users).
Tries to autodetect shell type (cmd, powershell, unix) on first use.
Implemented default builtin powershell functions for hashsum and about when remote
shell is powershell.
See #5763
Fixes #5758
---
backend/sftp/sftp.go | 326 +++++++++++++++++++++++------
backend/sftp/sftp_internal_test.go | 41 +++-
docs/content/sftp.md | 152 ++++++++++++--
3 files changed, 433 insertions(+), 86 deletions(-)
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index 9f7de872526d4..a75d91902d796 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -39,6 +39,8 @@ import (
)
const (
+ defaultShellType = "unix"
+ shellTypeNotSupported = "none"
hashCommandNotSupported = "none"
minSleep = 100 * time.Millisecond
maxSleep = 2 * time.Second
@@ -47,7 +49,9 @@ const (
)
var (
- currentUser = env.CurrentUser()
+ currentUser = env.CurrentUser()
+ posixWinAbsPathRegex = regexp.MustCompile(`^/[a-zA-Z]\:($|/)`) // E.g. "/C:" or anything starting with "/C:/"
+ unixShellEscapeRegex = regexp.MustCompile("[^A-Za-z0-9_.,:/\\@\u0080-\uFFFFFFFF\n-]")
)
func init() {
@@ -148,16 +152,16 @@ If this is set and no password is supplied then rclone will:
}, {
Name: "path_override",
Default: "",
- Help: `Override path used by SSH connection.
+ Help: `Override path used by SSH shell commands.
This allows checksum calculation when SFTP and SSH paths are
different. This issue affects among others Synology NAS boxes.
-Shared folders can be found in directories representing volumes
+E.g. if shared folders can be found in directories representing volumes:
rclone sync /home/local/directory remote:/directory --sftp-path-override /volume2/directory
-Home directory can be found in a shared folder called "home"
+E.g. if home directory can be found in a shared folder called "home":
rclone sync /home/local/directory remote:/home/directory --sftp-path-override /volume1/homes/USER/directory`,
Advanced: true,
@@ -166,6 +170,26 @@ Home directory can be found in a shared folder called "home"
Default: true,
Help: "Set the modified time on the remote if set.",
Advanced: true,
+ }, {
+ Name: "shell_type",
+ Default: "",
+ Help: "The type of SSH shell on remote server, if any.\n\nLeave blank for autodetect.",
+ Advanced: true,
+ Examples: []fs.OptionExample{
+ {
+ Value: shellTypeNotSupported,
+ Help: "No shell access",
+ }, {
+ Value: "unix",
+ Help: "Unix shell",
+ }, {
+ Value: "powershell",
+ Help: "PowerShell",
+ }, {
+ Value: "cmd",
+ Help: "Windows Command Prompt",
+ },
+ },
}, {
Name: "md5sum_command",
Default: "",
@@ -270,6 +294,7 @@ type Options struct {
AskPassword bool `config:"ask_password"`
PathOverride string `config:"path_override"`
SetModTime bool `config:"set_modtime"`
+ ShellType string `config:"shell_type"`
Md5sumCommand string `config:"md5sum_command"`
Sha1sumCommand string `config:"sha1sum_command"`
SkipLinks bool `config:"skip_links"`
@@ -286,6 +311,8 @@ type Fs struct {
name string
root string
absRoot string
+ shellRoot string
+ shellType string
opt Options // parsed options
ci *fs.ConfigInfo // global config
m configmap.Mapper // config
@@ -542,7 +569,7 @@ func (f *Fs) drainPool(ctx context.Context) (err error) {
f.drain.Stop()
}
if len(f.pool) != 0 {
- fs.Debugf(f, "closing %d unused connections", len(f.pool))
+ fs.Debugf(f, "Closing %d unused connections", len(f.pool))
}
for i, c := range f.pool {
if cErr := c.closed(); cErr == nil {
@@ -739,7 +766,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
//
// Just send the password back for all questions
func (f *Fs) keyboardInteractiveReponse(user, instruction string, questions []string, echos []bool, pass string) ([]string, error) {
- fs.Debugf(f, "keyboard interactive auth requested")
+ fs.Debugf(f, "Keyboard interactive auth requested")
answers := make([]string, len(questions))
for i := range answers {
answers[i] = pass
@@ -769,6 +796,7 @@ func NewFsWithConnection(ctx context.Context, f *Fs, name string, root string, m
f.name = name
f.root = root
f.absRoot = root
+ f.shellRoot = root
f.opt = *opt
f.m = m
f.config = sshConfig
@@ -778,7 +806,7 @@ func NewFsWithConnection(ctx context.Context, f *Fs, name string, root string, m
f.savedpswd = ""
// set the pool drainer timer going
if f.opt.IdleTimeout > 0 {
- f.drain = time.AfterFunc(time.Duration(opt.IdleTimeout), func() { _ = f.drainPool(ctx) })
+ f.drain = time.AfterFunc(time.Duration(f.opt.IdleTimeout), func() { _ = f.drainPool(ctx) })
}
f.features = (&fs.Features{
@@ -790,16 +818,59 @@ func NewFsWithConnection(ctx context.Context, f *Fs, name string, root string, m
if err != nil {
return nil, fmt.Errorf("NewFs: %w", err)
}
- cwd, err := c.sftpClient.Getwd()
- f.putSftpConnection(&c, nil)
- if err != nil {
- fs.Debugf(f, "Failed to read current directory - using relative paths: %v", err)
- } else if !path.IsAbs(f.root) {
- f.absRoot = path.Join(cwd, f.root)
- fs.Debugf(f, "Using absolute root directory %q", f.absRoot)
+ // Check remote shell type, try to auto-detect if not configured and save to config for later
+ if f.opt.ShellType != "" {
+ f.shellType = f.opt.ShellType
+ fs.Debugf(f, "Shell type %q from config", f.shellType)
+ } else {
+ session, err := c.sshClient.NewSession()
+ if err != nil {
+ f.shellType = shellTypeNotSupported
+ fs.Debugf(f, "Failed to get shell session for shell type detection command: %v", err)
+ } else {
+ var stdout, stderr bytes.Buffer
+ session.Stdout = &stdout
+ session.Stderr = &stderr
+ shellCmd := "echo ${ShellId}%ComSpec%"
+ fs.Debugf(f, "Running shell type detection remote command: %s", shellCmd)
+ err = session.Run(shellCmd)
+ _ = session.Close()
+ if err != nil {
+ f.shellType = defaultShellType
+ fs.Debugf(f, "Remote command failed: %v (stdout=%v) (stderr=%v)", err, bytes.TrimSpace(stdout.Bytes()), bytes.TrimSpace(stderr.Bytes()))
+ } else {
+ outBytes := stdout.Bytes()
+ fs.Debugf(f, "Remote command result: %s", outBytes)
+ outString := string(bytes.TrimSpace(stdout.Bytes()))
+ if strings.HasPrefix(outString, "Microsoft.PowerShell") { // If PowerShell: "Microsoft.PowerShell%ComSpec%"
+ f.shellType = "powershell"
+ } else if !strings.HasSuffix(outString, "%ComSpec%") { // If Command Prompt: "${ShellId}C:\WINDOWS\system32\cmd.exe"
+ f.shellType = "cmd"
+ } else { // If Unix: "%ComSpec%"
+ f.shellType = "unix"
+ }
+ }
+ }
+ // Save permanently in config to avoid the extra work next time
+ fs.Debugf(f, "Shell type %q detected (set option shell_type to override)", f.shellType)
+ f.m.Set("shell_type", f.shellType)
+ }
+ // Ensure we have absolute path to root
+ // It appears that WS FTP doesn't like relative paths,
+ // and the openssh sftp tool also uses absolute paths.
+ if !path.IsAbs(f.root) {
+ path, err := c.sftpClient.RealPath(f.root)
+ if err != nil {
+ fs.Debugf(f, "Failed to resolve path - using relative paths: %v", err)
+ } else {
+ f.absRoot = path
+ fs.Debugf(f, "Relative path resolved to %q", f.absRoot)
+ }
}
+ f.putSftpConnection(&c, err)
if root != "" {
- // Check to see if the root actually an existing file
+ // Check to see if the root is actually an existing file,
+ // and if so change the filesystem root to its parent directory.
oldAbsRoot := f.absRoot
remote := path.Base(root)
f.root = path.Dir(root)
@@ -807,20 +878,24 @@ func NewFsWithConnection(ctx context.Context, f *Fs, name string, root string, m
if f.root == "." {
f.root = ""
}
- _, err := f.NewObject(ctx, remote)
+ _, err = f.NewObject(ctx, remote)
if err != nil {
- if err == fs.ErrorObjectNotFound || err == fs.ErrorIsDir {
- // File doesn't exist so return old f
- f.root = root
- f.absRoot = oldAbsRoot
- return f, nil
+ if err != fs.ErrorObjectNotFound && err != fs.ErrorIsDir {
+ return nil, err
}
- return nil, err
+ // File doesn't exist so keep the old f
+ f.root = root
+ f.absRoot = oldAbsRoot
+ err = nil
+ } else {
+ // File exists so change fs to point to the parent and return it with an error
+ err = fs.ErrorIsFile
}
- // return an error with an fs which points to the parent
- return f, fs.ErrorIsFile
+ } else {
+ err = nil
}
- return f, nil
+ fs.Debugf(f, "Using root directory %q", f.absRoot)
+ return f, err
}
// Name returns the configured name of the file system
@@ -1155,74 +1230,147 @@ func (f *Fs) run(ctx context.Context, cmd string) ([]byte, error) {
// Hashes returns the supported hash types of the filesystem
func (f *Fs) Hashes() hash.Set {
ctx := context.TODO()
- if f.opt.DisableHashCheck {
- return hash.Set(hash.None)
- }
if f.cachedHashes != nil {
return *f.cachedHashes
}
+ hashSet := hash.NewHashSet()
+ f.cachedHashes = &hashSet
+
+ if f.opt.DisableHashCheck || f.shellType == shellTypeNotSupported {
+ return hashSet
+ }
+
// look for a hash command which works
- checkHash := func(commands []string, expected string, hashCommand *string, changed *bool) bool {
+ checkHash := func(hashType hash.Type, commands []struct{ hashFile, hashEmpty string }, expected string, hashCommand *string, changed *bool) bool {
if *hashCommand == hashCommandNotSupported {
return false
}
if *hashCommand != "" {
return true
}
+ fs.Debugf(f, "Checking default %v hash commands", hashType)
*changed = true
for _, command := range commands {
- output, err := f.run(ctx, command)
+ output, err := f.run(ctx, command.hashEmpty)
if err != nil {
+ fs.Debugf(f, "Hash command skipped: %v", err)
continue
}
output = bytes.TrimSpace(output)
- fs.Debugf(f, "checking %q command: %q", command, output)
if parseHash(output) == expected {
- *hashCommand = command
+ *hashCommand = command.hashFile
+ fs.Debugf(f, "Hash command accepted")
return true
}
+ fs.Debugf(f, "Hash command skipped: Wrong output")
}
*hashCommand = hashCommandNotSupported
return false
}
changed := false
- md5Works := checkHash([]string{"md5sum", "md5 -r", "rclone md5sum"}, "d41d8cd98f00b204e9800998ecf8427e", &f.opt.Md5sumCommand, &changed)
- sha1Works := checkHash([]string{"sha1sum", "sha1 -r", "rclone sha1sum"}, "da39a3ee5e6b4b0d3255bfef95601890afd80709", &f.opt.Sha1sumCommand, &changed)
+ md5Commands := []struct {
+ hashFile, hashEmpty string
+ }{
+ {"md5sum", "md5sum"},
+ {"md5 -r", "md5 -r"},
+ {"rclone md5sum", "rclone md5sum"},
+ }
+ sha1Commands := []struct {
+ hashFile, hashEmpty string
+ }{
+ {"sha1sum", "sha1sum"},
+ {"sha1 -r", "sha1 -r"},
+ {"rclone sha1sum", "rclone sha1sum"},
+ }
+ if f.shellType == "powershell" {
+ md5Commands = append(md5Commands, struct {
+ hashFile, hashEmpty string
+ }{
+ "&{param($Path);Get-FileHash -Algorithm MD5 -LiteralPath $Path -ErrorAction Stop|Select-Object -First 1 -ExpandProperty Hash|ForEach-Object{\"$($_.ToLower()) ${Path}\"}}",
+ "Get-FileHash -Algorithm MD5 -InputStream ([System.IO.MemoryStream]::new()) -ErrorAction Stop|Select-Object -First 1 -ExpandProperty Hash|ForEach-Object{$_.ToLower()}",
+ })
+
+ sha1Commands = append(sha1Commands, struct {
+ hashFile, hashEmpty string
+ }{
+ "&{param($Path);Get-FileHash -Algorithm SHA1 -LiteralPath $Path -ErrorAction Stop|Select-Object -First 1 -ExpandProperty Hash|ForEach-Object{\"$($_.ToLower()) ${Path}\"}}",
+ "Get-FileHash -Algorithm SHA1 -InputStream ([System.IO.MemoryStream]::new()) -ErrorAction Stop|Select-Object -First 1 -ExpandProperty Hash|ForEach-Object{$_.ToLower()}",
+ })
+ }
+
+ md5Works := checkHash(hash.MD5, md5Commands, "d41d8cd98f00b204e9800998ecf8427e", &f.opt.Md5sumCommand, &changed)
+ sha1Works := checkHash(hash.SHA1, sha1Commands, "da39a3ee5e6b4b0d3255bfef95601890afd80709", &f.opt.Sha1sumCommand, &changed)
if changed {
+ // Save permanently in config to avoid the extra work next time
+ fs.Debugf(f, "Setting hash command for %v to %q (set sha1sum_command to override)", hash.MD5, f.opt.Md5sumCommand)
f.m.Set("md5sum_command", f.opt.Md5sumCommand)
+ fs.Debugf(f, "Setting hash command for %v to %q (set md5sum_command to override)", hash.SHA1, f.opt.Sha1sumCommand)
f.m.Set("sha1sum_command", f.opt.Sha1sumCommand)
}
- set := hash.NewHashSet()
if sha1Works {
- set.Add(hash.SHA1)
+ hashSet.Add(hash.SHA1)
}
if md5Works {
- set.Add(hash.MD5)
+ hashSet.Add(hash.MD5)
}
- f.cachedHashes = &set
- return set
+ return hashSet
}
// About gets usage stats
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
- escapedPath := shellEscape(f.root)
- if f.opt.PathOverride != "" {
- escapedPath = shellEscape(path.Join(f.opt.PathOverride, f.root))
+ if f.shellType == shellTypeNotSupported || f.shellType == "cmd" {
+ fs.Debugf(f, "About shell command is not available for shell type %q (set option shell_type to override)", f.shellType)
+ return nil, fmt.Errorf("not supported with shell type %q", f.shellType)
}
- if len(escapedPath) == 0 {
- escapedPath = "/"
+ aboutShellPath := f.remoteShellPath("")
+ if aboutShellPath == "" {
+ aboutShellPath = "/"
}
- stdout, err := f.run(ctx, "df -k "+escapedPath)
+ fs.Debugf(f, "About path %q", aboutShellPath)
+ aboutShellPathArg, err := f.quoteOrEscapeShellPath(aboutShellPath)
if err != nil {
+ return nil, err
+ }
+ // PowerShell
+ if f.shellType == "powershell" {
+ shellCmd := "Get-Item " + aboutShellPathArg + " -ErrorAction Stop|Select-Object -First 1 -ExpandProperty PSDrive|ForEach-Object{\"$($_.Used) $($_.Free)\"}"
+ fs.Debugf(f, "About using shell command for shell type %q", f.shellType)
+ stdout, err := f.run(ctx, shellCmd)
+ if err != nil {
+ fs.Debugf(f, "About shell command for shell type %q failed (set option shell_type to override): %v", f.shellType, err)
+ return nil, fmt.Errorf("powershell command failed: %w", err)
+ }
+ split := strings.Fields(string(stdout))
+ usage := &fs.Usage{}
+ if len(split) == 2 {
+ usedValue, usedErr := strconv.ParseInt(split[0], 10, 64)
+ if usedErr == nil {
+ usage.Used = fs.NewUsageValue(usedValue)
+ }
+ freeValue, freeErr := strconv.ParseInt(split[1], 10, 64)
+ if freeErr == nil {
+ usage.Free = fs.NewUsageValue(freeValue)
+ if usedErr == nil {
+ usage.Total = fs.NewUsageValue(usedValue + freeValue)
+ }
+ }
+ }
+ return usage, nil
+ }
+ // Unix/default shell
+ shellCmd := "df -k " + aboutShellPathArg
+ fs.Debugf(f, "About using shell command for shell type %q", f.shellType)
+ stdout, err := f.run(ctx, shellCmd)
+ if err != nil {
+ fs.Debugf(f, "About shell command for shell type %q failed (set option shell_type to override): %v", f.shellType, err)
return nil, fmt.Errorf("your remote may not have the required df utility: %w", err)
}
-
usageTotal, usageUsed, usageAvail := parseUsage(stdout)
usage := &fs.Usage{}
if usageTotal >= 0 {
@@ -1287,31 +1435,78 @@ func (o *Object) Hash(ctx context.Context, r hash.Type) (string, error) {
return "", hash.ErrUnsupported
}
- escapedPath := shellEscape(o.path())
- if o.fs.opt.PathOverride != "" {
- escapedPath = shellEscape(path.Join(o.fs.opt.PathOverride, o.remote))
+ shellPathArg, err := o.fs.quoteOrEscapeShellPath(o.shellPath())
+ if err != nil {
+ return "", fmt.Errorf("failed to calculate %v hash: %w", r, err)
}
- b, err := o.fs.run(ctx, hashCmd+" "+escapedPath)
+ outBytes, err := o.fs.run(ctx, hashCmd+" "+shellPathArg)
if err != nil {
return "", fmt.Errorf("failed to calculate %v hash: %w", r, err)
}
-
- str := parseHash(b)
+ hashString := parseHash(outBytes)
+ fs.Debugf(o, "Parsed hash: %s", hashString)
if r == hash.MD5 {
- o.md5sum = &str
+ o.md5sum = &hashString
} else if r == hash.SHA1 {
- o.sha1sum = &str
+ o.sha1sum = &hashString
+ }
+ return hashString, nil
+}
+
+// quoteOrEscapeShellPath makes path a valid string argument in configured shell
+// and also ensures it cannot cause unintended behavior.
+func quoteOrEscapeShellPath(shellType string, shellPath string) (string, error) {
+ // PowerShell
+ if shellType == "powershell" {
+ return "'" + strings.ReplaceAll(shellPath, "'", "''") + "'", nil
}
- return str, nil
+ // Windows Command Prompt
+ if shellType == "cmd" {
+ if strings.Contains(shellPath, "\"") {
+ return "", fmt.Errorf("path is not valid in shell type %s: %s", shellType, shellPath)
+ }
+ return "\"" + shellPath + "\"", nil
+ }
+ // Unix shell
+ safe := unixShellEscapeRegex.ReplaceAllString(shellPath, `\$0`)
+ return strings.ReplaceAll(safe, "\n", "'\n'"), nil
+}
+
+// quoteOrEscapeShellPath makes path a valid string argument in configured shell
+func (f *Fs) quoteOrEscapeShellPath(shellPath string) (string, error) {
+ return quoteOrEscapeShellPath(f.shellType, shellPath)
}
-var shellEscapeRegex = regexp.MustCompile("[^A-Za-z0-9_.,:/\\@\u0080-\uFFFFFFFF\n-]")
+// remotePath returns the native SFTP path of the file or directory at the remote given
+func (f *Fs) remotePath(remote string) string {
+ return path.Join(f.absRoot, remote)
+}
-// Escape a string s.t. it cannot cause unintended behavior
-// when sending it to a shell.
-func shellEscape(str string) string {
- safe := shellEscapeRegex.ReplaceAllString(str, `\$0`)
- return strings.ReplaceAll(safe, "\n", "'\n'")
+// remoteShellPath returns the SSH shell path of the file or directory at the remote given
+func (f *Fs) remoteShellPath(remote string) string {
+ if f.opt.PathOverride != "" {
+ shellPath := path.Join(f.opt.PathOverride, remote)
+ fs.Debugf(f, "Shell path redirected to %q with option path_override", shellPath)
+ return shellPath
+ }
+ shellPath := path.Join(f.absRoot, remote)
+ if f.shellType == "powershell" || f.shellType == "cmd" {
+ // If remote shell is powershell or cmd, then server is probably Windows.
+ // The sftp package converts everything to POSIX paths: Forward slashes, and
+ // absolute paths starts with a slash. An absolute path on a Windows server will
+ // then look like this "/C:/Windows/System32". We must remove the "/" prefix
+ // to make this a valid path for shell commands. In case of PowerShell there is a
+ // possibility that it is a Unix server, with PowerShell Core shell, but assuming
+ // root folders with names such as "C:" are rare, we just take this risk,
+ // and option path_override can always be used to work around corner cases.
+ if posixWinAbsPathRegex.MatchString(shellPath) {
+ shellPath = strings.TrimPrefix(shellPath, "/")
+ fs.Debugf(f, "Shell path adjusted to %q (set option path_override to override)", shellPath)
+ return shellPath
+ }
+ }
+ fs.Debugf(f, "Shell path %q", shellPath)
+ return shellPath
}
// Converts a byte array from the SSH session returned by
@@ -1362,9 +1557,14 @@ func (o *Object) ModTime(ctx context.Context) time.Time {
return o.modTime
}
-// path returns the native path of the object
+// path returns the native SFTP path of the object
func (o *Object) path() string {
- return path.Join(o.fs.absRoot, o.remote)
+ return o.fs.remotePath(o.remote)
+}
+
+// shellPath returns the SSH shell path of the object
+func (o *Object) shellPath() string {
+ return o.fs.remoteShellPath(o.remote)
}
// setMetadata updates the info in the object from the stat result passed in
diff --git a/backend/sftp/sftp_internal_test.go b/backend/sftp/sftp_internal_test.go
index e73cc417efe18..0803321c8b947 100644
--- a/backend/sftp/sftp_internal_test.go
+++ b/backend/sftp/sftp_internal_test.go
@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestShellEscape(t *testing.T) {
+func TestShellEscapeUnix(t *testing.T) {
for i, test := range []struct {
unescaped, escaped string
}{
@@ -20,7 +20,44 @@ func TestShellEscape(t *testing.T) {
{"/test/\n", "/test/'\n'"},
{":\"'", ":\\\"\\'"},
} {
- got := shellEscape(test.unescaped)
+ got, err := quoteOrEscapeShellPath("unix", test.unescaped)
+ assert.NoError(t, err)
+ assert.Equal(t, test.escaped, got, fmt.Sprintf("Test %d unescaped = %q", i, test.unescaped))
+ }
+}
+
+func TestShellEscapeCmd(t *testing.T) {
+ for i, test := range []struct {
+ unescaped, escaped string
+ ok bool
+ }{
+ {"", "\"\"", true},
+ {"c:/this/is/harmless", "\"c:/this/is/harmless\"", true},
+ {"c:/test¬epad", "\"c:/test¬epad\"", true},
+ {"c:/test\"&\"notepad", "", false},
+ } {
+ got, err := quoteOrEscapeShellPath("cmd", test.unescaped)
+ if test.ok {
+ assert.NoError(t, err)
+ assert.Equal(t, test.escaped, got, fmt.Sprintf("Test %d unescaped = %q", i, test.unescaped))
+ } else {
+ assert.Error(t, err)
+ }
+ }
+}
+
+func TestShellEscapePowerShell(t *testing.T) {
+ for i, test := range []struct {
+ unescaped, escaped string
+ }{
+ {"", "''"},
+ {"c:/this/is/harmless", "'c:/this/is/harmless'"},
+ {"c:/test¬epad", "'c:/test¬epad'"},
+ {"c:/test\"&\"notepad", "'c:/test\"&\"notepad'"},
+ {"c:/test'&'notepad", "'c:/test''&''notepad'"},
+ } {
+ got, err := quoteOrEscapeShellPath("powershell", test.unescaped)
+ assert.NoError(t, err)
assert.Equal(t, test.escaped, got, fmt.Sprintf("Test %d unescaped = %q", i, test.unescaped))
}
}
diff --git a/docs/content/sftp.md b/docs/content/sftp.md
index 5bc27dcd88fc7..ecd21899087e8 100644
--- a/docs/content/sftp.md
+++ b/docs/content/sftp.md
@@ -28,6 +28,9 @@ Note that some SFTP servers will need the leading / - Synology is a
good example of this. rsync.net, on the other hand, requires users to
OMIT the leading /.
+Note that by default rclone will try to execute shell commands on
+the server, see [shell access considerations](#shell-access-considerations).
+
## Configuration
Here is an example of making an SFTP configuration. First run
@@ -244,6 +247,116 @@ And then at the end of the session
These commands can be used in scripts of course.
+### Shell access
+
+Some functionality of the SFTP backend relies on remote shell access,
+and the possibility to execute commands. This includes [checksum](#checksum),
+and in some cases also [about](#about-command). The shell commands that
+must be executed may be different on different type of shells, and also
+quoting/escaping of file path arguments containing special characters may
+be different. Rclone therefore needs to know what type of shell it is,
+and if shell access is available at all.
+
+Most servers run on some version of Unix, and then a basic Unix shell can
+be assumed, without further distinction. Windows 10, Server 2019, and later
+can also run a SSH server, which is a port of OpenSSH (see official
+[installation guide](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse)). On a Windows server the shell handling is different: Although it can also
+be set up to use a Unix type shell, e.g. Cygwin bash, the default is to
+use Windows Command Prompt (cmd.exe), and PowerShell is a recommended
+alternative. All of these have bahave differently, which rclone must handle.
+
+Rclone tries to auto-detect what type of shell is used on the server,
+first time you access the SFTP remote. If a remote shell session is
+successfully created, it will look for indications that it is CMD or
+PowerShell, with fall-back to Unix if not something else is detected.
+If unable to even create a remote shell session, then shell command
+execution will be disabled entirely. The result is stored in the SFTP
+remote configuration, in option `shell_type`, so that the auto-detection
+only have to be performed once. If you manually set a value for this
+option before first run, the auto-detection will be skipped, and if
+you set a different value later this will override any existing.
+Value `none` can be set to avoid any attempts at executing shell
+commands, e.g. if this is not allowed on the server.
+
+When the server is [rclone serve sftp](/commands/rclone_serve_sftp/),
+the rclone SFTP remote will detect this as a Unix type shell - even
+if it is running on Windows. This server does not actually have a shell,
+but it accepts input commands matching the specific ones that the
+SFTP backend relies on for Unix shells, e.g. `md5sum` and `df`. Also
+it handles the string escape rules used for Unix shell. Treating it
+as a Unix type shell from a SFTP remote will therefore always be
+correct, and support all features.
+
+#### Shell access considerations
+
+The shell type auto-detection logic, described above, means that
+by default rclone will try to run a shell command the first time
+a new sftp remote is accessed. If you configure a sftp remote
+without a config file, e.g. an [on the fly](/docs/#backend-path-to-dir])
+remote, rclone will have nowhere to store the result, and it
+will re-run the command on every access. To avoid this you should
+explicitely set the `shell_type` option to the correct value,
+or to `none` if you want to prevent rclone from executing any
+remote shell commands.
+
+It is also important to note that, since the shell type decides
+how quoting and escaping of file paths used as command-line arguments
+are performed, configuring the wrong shell type may leave you exposed
+to command injection exploits. Make sure to confirm the auto-detected
+shell type, or explicitely set the shell type you know is correct,
+or disable shell access until you know.
+
+### Checksum
+
+SFTP does not natively support checksums (file hash), but rclone
+is able to use checksumming if the same login has shell access,
+and can execute remote commands. If there is a command that can
+calculate compatible checksums on the remote system, Rclone can
+then be configured to execute this whenever a checksum is needed,
+and read back the results. Currently MD5 and SHA-1 are supported.
+
+Normally this requires an external utility being available on
+the server. By default rclone will try commands `md5sum`, `md5`
+and `rclone md5sum` for MD5 checksums, and the first one found usable
+will be picked. Same with `sha1sum`, `sha1` and `rclone sha1sum`
+commands for SHA-1 checksums. These utilities normally need to
+be in the remote's PATH to be found.
+
+In some cases the shell itself is capable of calculating checksums.
+PowerShell is an example of such a shell. If rclone detects that the
+remote shell is PowerShell, which means it most probably is a
+Windows OpenSSH server, rclone will use a predefined script block
+to produce the checksums when no external checksum commands are found
+(see [shell access](#shell-access)). This assumes PowerShell version
+4.0 or newer.
+
+The options `md5sum_command` and `sha1_command` can be used to customize
+the command to be executed for calculation of checksums. You can for
+example set a specific path to where md5sum and sha1sum executables
+are located, or use them to specify some other tools that print checksums
+in compatible format. The value can include command-line arguments,
+or even shell script blocks as with PowerShell. Rclone has subcommands
+[md5sum](/commands/rclone_md5sum/) and [sha1sum](/commands/rclone_sha1sum/)
+that use compatible format, which means if you have an rclone executable
+on the server it can be used. As mentioned above, they will be automatically
+picked up if found in PATH, but if not you can set something like
+`/path/to/rclone md5sum` as the value of option `md5sum_command` to
+make sure a specific executable is used.
+
+Remote checksumming is recommended and enabled by default. First time
+rclone is using a SFTP remote, if options `md5sum_command` or `sha1_command`
+are not set, it will check if any of the default commands for each of them,
+as described above, can be used. The result will be saved in the remote
+configuration, so next time it will use the same. Value `none`
+will be set if none of the default commands could be used for a specific
+algorithm, and this algorithm will not be supported by the remote.
+
+Disabling the checksumming may be required if you are connecting to SFTP servers
+which are not under your control, and to which the execution of remote shell
+commands is prohibited. Set the configuration option `disable_hashcheck`
+to `true` to disable checksumming entirely, or set `shell_type` to `none`
+to disable all functionality based on remote shell command execution.
+
### Modified time
Modified times are stored on the server to 1 second precision.
@@ -255,6 +368,20 @@ upload (for example, certain configurations of ProFTPd with mod_sftp). If you
are using one of these servers, you can set the option `set_modtime = false` in
your RClone backend configuration to disable this behaviour.
+### About command
+
+SFTP supports the [about](/commands/rclone_about/) command if the
+same login has access to a Unix shell, where the `df` command is
+available (e.g. in the remote's PATH). `about` will return the
+total space, free space, and used space on the remote for the disk
+of the specified path on the remote or, if not set, the disk of
+the root on the remote. `about` will fail if it does not have
+shell access or if `df` is not found.
+
+If the server shell is PowerShell, probably with a Windows OpenSSH
+server, rclone supports `about` using a built-in shell command
+(see [shell access](#shell-access)).
+
{{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/sftp/sftp.go then run make backenddocs" >}}
### Standard options
@@ -637,25 +764,9 @@ Properties:
## Limitations
-SFTP supports checksums if the same login has shell access and `md5sum`
-or `sha1sum` as well as `echo` are in the remote's PATH.
-This remote checksumming (file hashing) is recommended and enabled by default.
-Disabling the checksumming may be required if you are connecting to SFTP servers
-which are not under your control, and to which the execution of remote commands
-is prohibited. Set the configuration option `disable_hashcheck` to `true` to
-disable checksumming.
-
-SFTP also supports `about` if the same login has shell
-access and `df` are in the remote's PATH. `about` will
-return the total space, free space, and used space on the remote
-for the disk of the specified path on the remote or, if not set,
-the disk of the root on the remote.
-`about` will fail if it does not have shell
-access or if `df` is not in the remote's PATH.
-
-Note that some SFTP servers (e.g. Synology) the paths are different for
-SSH and SFTP so the hashes can't be calculated properly. For them
-using `disable_hashcheck` is a good idea.
+On some SFTP servers (e.g. Synology) the paths are different
+for SSH and SFTP so the hashes can't be calculated properly.
+For them using `disable_hashcheck` is a good idea.
The only ssh agent supported under Windows is Putty's pageant.
@@ -670,11 +781,10 @@ SFTP isn't supported under plan9 until [this
issue](https://github.com/pkg/sftp/issues/156) is fixed.
Note that since SFTP isn't HTTP based the following flags don't work
-with it: `--dump-headers`, `--dump-bodies`, `--dump-auth`
+with it: `--dump-headers`, `--dump-bodies`, `--dump-auth`.
Note that `--timeout` and `--contimeout` are both supported.
-
## rsync.net {#rsync-net}
rsync.net is supported through the SFTP backend.
From 5db9a2f8313ce240daf0a1b2fa8c5337bc291c9d Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 31 Oct 2021 14:04:36 +0100
Subject: [PATCH 024/560] sftp: use vendor-specific VFS statistics extension
for about if available
See #5763
---
backend/sftp/sftp.go | 36 ++++++++++++++++++++++++++++++++++++
docs/content/sftp.md | 24 +++++++++++++-----------
2 files changed, 49 insertions(+), 11 deletions(-)
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index a75d91902d796..305fbde983925 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -1324,6 +1324,42 @@ func (f *Fs) Hashes() hash.Set {
// About gets usage stats
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
+ // If server implements the vendor-specific VFS statistics extension prefer that
+ // (OpenSSH implements it on using syscall.Statfs on Linux and API function GetDiskFreeSpace on Windows)
+ c, err := f.getSftpConnection(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var vfsStats *sftp.StatVFS
+ if _, found := c.sftpClient.HasExtension("statvfs@openssh.com"); found {
+ fs.Debugf(f, "Server has VFS statistics extension")
+ aboutPath := f.absRoot
+ if aboutPath == "" {
+ aboutPath = "/"
+ }
+ fs.Debugf(f, "About path %q", aboutPath)
+ vfsStats, err = c.sftpClient.StatVFS(aboutPath)
+ }
+ f.putSftpConnection(&c, err) // Return to pool asap, if running shell command below it will be re-used
+ if vfsStats != nil {
+ total := vfsStats.TotalSpace()
+ free := vfsStats.FreeSpace()
+ used := total - free
+ return &fs.Usage{
+ Total: fs.NewUsageValue(int64(total)),
+ Used: fs.NewUsageValue(int64(used)),
+ Free: fs.NewUsageValue(int64(free)),
+ }, nil
+ } else if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return nil, err
+ }
+ fs.Debugf(f, "Failed to retrieve VFS statistics, trying shell command instead: %v", err)
+ } else {
+ fs.Debugf(f, "Server does not have the VFS statistics extension, trying shell command instead")
+ }
+
+ // Fall back to shell command method if possible
if f.shellType == shellTypeNotSupported || f.shellType == "cmd" {
fs.Debugf(f, "About shell command is not available for shell type %q (set option shell_type to override)", f.shellType)
return nil, fmt.Errorf("not supported with shell type %q", f.shellType)
diff --git a/docs/content/sftp.md b/docs/content/sftp.md
index ecd21899087e8..a0fc7271cb92e 100644
--- a/docs/content/sftp.md
+++ b/docs/content/sftp.md
@@ -370,17 +370,19 @@ your RClone backend configuration to disable this behaviour.
### About command
-SFTP supports the [about](/commands/rclone_about/) command if the
-same login has access to a Unix shell, where the `df` command is
-available (e.g. in the remote's PATH). `about` will return the
-total space, free space, and used space on the remote for the disk
-of the specified path on the remote or, if not set, the disk of
-the root on the remote. `about` will fail if it does not have
-shell access or if `df` is not found.
-
-If the server shell is PowerShell, probably with a Windows OpenSSH
-server, rclone supports `about` using a built-in shell command
-(see [shell access](#shell-access)).
+The `about` command returns the total space, free space, and used
+space on the remote for the disk of the specified path on the remote or,
+if not set, the disk of the root on the remote.
+
+SFTP usually supports the [about](/commands/rclone_about/) command, but
+it depends on the server. If the server implements the vendor-specific
+VFS statistics extension, which is normally the case with OpenSSH instances,
+it will be used. If not, but the same login has access to a Unix shell,
+where the `df` command is available (e.g. in the remote's PATH), then
+this will be used instead. If the server shell is PowerShell, probably
+with a Windows OpenSSH server, rclone will use a built-in shell command
+(see [shell access](#shell-access)). If none of the above is applicable,
+`about` will fail.
{{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/sftp/sftp.go then run make backenddocs" >}}
### Standard options
From 7c1f2d7c841b56136987a44ca58f5d0505ccb00d Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 9 Jun 2022 12:45:24 +0100
Subject: [PATCH 025/560] dirtree: fix tests with -fast-list
In commit
da404dc0f28e919c sync,copy: Fix --fast-list --create-empty-src-dirs and --exclude
The fix caused DirTree.AddDir to be called with the root directory.
This in turn caused a spurious directory entry in the DirTree which
caused tests with the -fast-list flag to fail with directory not found
errors.
---
fs/dirtree/dirtree.go | 5 ++++-
fs/dirtree/dirtree_test.go | 8 ++++++++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/fs/dirtree/dirtree.go b/fs/dirtree/dirtree.go
index cb78ab9df6b34..2785dcbed2ec8 100644
--- a/fs/dirtree/dirtree.go
+++ b/fs/dirtree/dirtree.go
@@ -40,9 +40,12 @@ func (dt DirTree) Add(entry fs.DirEntry) {
// this creates the directory itself if required
// it doesn't create parents
func (dt DirTree) AddDir(entry fs.DirEntry) {
+ dirPath := entry.Remote()
+ if dirPath == "" {
+ return
+ }
dt.Add(entry)
// create the directory itself if it doesn't exist already
- dirPath := entry.Remote()
if _, ok := dt[dirPath]; !ok {
dt[dirPath] = nil
}
diff --git a/fs/dirtree/dirtree_test.go b/fs/dirtree/dirtree_test.go
index b292ade0aa67e..d548d8c6f4728 100644
--- a/fs/dirtree/dirtree_test.go
+++ b/fs/dirtree/dirtree_test.go
@@ -51,6 +51,14 @@ func TestDirTreeAddDir(t *testing.T) {
dir/subdir/
sausage/
dir/subdir/sausage/
+`, dt.String())
+ d = mockdir.New("")
+ dt.AddDir(d)
+ assert.Equal(t, `/
+ potato/
+dir/subdir/
+ sausage/
+dir/subdir/sausage/
`, dt.String())
}
From cfe0911e0d9fbfc8a931c981216021689e53595f Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 9 Jun 2022 13:19:11 +0100
Subject: [PATCH 026/560] sync: fix tests for overlapping with filter
In commit
3ccf222acb9697c3 sync: overlap check is now filter-sensitive
The tests were attempting to write invalid objects on some backends
due to a leading / on the object name.
This fix also adds a few more test cases and makes sure the tests can
be run individually.
---
fs/operations/operations_test.go | 3 +++
fs/sync/sync_test.go | 4 +++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 83df26398a5e6..10475562aef2d 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -1284,6 +1284,7 @@ func TestOverlappingFilterCheckWithoutFilter(t *testing.T) {
expected bool
}{
{"name", "root", true},
+ {"name", "/root", true},
{"namey", "root", false},
{"name", "rooty", false},
{"namey", "rooty", false},
@@ -1320,10 +1321,12 @@ func TestOverlappingFilterCheckWithFilter(t *testing.T) {
expected bool
}{
{"name", "root", true},
+ {"name", "/root", true},
{"name", "root/", true},
{"name", "root" + slash, true},
{"name", "root/exclude", false},
{"name", "root/exclude/", false},
+ {"name", "/root/exclude/", false},
{"name", "root" + slash + "exclude", false},
{"name", "root" + slash + "exclude" + slash, false},
{"name", "root/.ignore", false},
diff --git a/fs/sync/sync_test.go b/fs/sync/sync_test.go
index 19e225fd92a76..4bf60c47121da 100644
--- a/fs/sync/sync_test.go
+++ b/fs/sync/sync_test.go
@@ -1469,18 +1469,20 @@ func TestSyncOverlapWithFilter(t *testing.T) {
FremoteSync3, err := fs.NewFs(ctx, subRemoteName3)
require.NoError(t, FremoteSync3.Mkdir(ctx, ""))
require.NoError(t, err)
- r.WriteObject(context.Background(), "/rclone-sync-test-ignore-file/.ignore", "-", t1)
+ r.WriteObject(context.Background(), "rclone-sync-test-ignore-file/.ignore", "-", t1)
checkErr := func(err error) {
require.Error(t, err)
assert.True(t, fserrors.IsFatalError(err))
assert.Equal(t, fs.ErrorOverlapping.Error(), err.Error())
+ accounting.GlobalStats().ResetCounters()
}
checkNoErr := func(err error) {
require.NoError(t, err)
}
+ accounting.GlobalStats().ResetCounters()
checkNoErr(Sync(ctx, FremoteSync, r.Fremote, false))
checkErr(Sync(ctx, r.Fremote, FremoteSync, false))
checkErr(Sync(ctx, r.Fremote, r.Fremote, false))
From aeb5dc28926c5fedffa4b68c54eac8d8d26afcb7 Mon Sep 17 00:00:00 2001
From: buengese
Date: Sun, 12 Jun 2022 15:28:36 +0200
Subject: [PATCH 027/560] docs/zoho: add a section explaining client_id setup
---
docs/content/zoho.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/docs/content/zoho.md b/docs/content/zoho.md
index 27199b6a08a21..ba155dfed10f3 100644
--- a/docs/content/zoho.md
+++ b/docs/content/zoho.md
@@ -234,3 +234,15 @@ Properties:
- Default: Del,Ctl,InvalidUtf8
{{< rem autogenerated options stop >}}
+
+## Setting up your own client_id
+
+For Zoho we advise you to set up your own client_id. To do so you have to complete the following steps.
+
+1. Log in to the [Zoho API Console](https://api-console.zoho.com)
+
+2. Create a new client of type "Server-based Application". The name and website don't matter, but you must add the redirect URL `http://localhost:53682/`.
+
+3. Once the client is created, you can go to the settings tab and enable it in other regions.
+
+The client id and client secret can now be used with rclone.
From cee79f27eef840d5efa005be68b632c5de16b43b Mon Sep 17 00:00:00 2001
From: buengese
Date: Sun, 12 Jun 2022 15:37:30 +0200
Subject: [PATCH 028/560] zoho: add Japan and China regions
---
backend/zoho/zoho.go | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/backend/zoho/zoho.go b/backend/zoho/zoho.go
index 2132037c6f2e2..e0082cacd44d8 100644
--- a/backend/zoho/zoho.go
+++ b/backend/zoho/zoho.go
@@ -172,6 +172,12 @@ browser.`,
}, {
Value: "in",
Help: "India",
+ }, {
+ Value: "jp",
+ Help: "Japan",
+ }, {
+ Value: "com.cn",
+ Help: "China",
}, {
Value: "com.au",
Help: "Australia",
From 3a20929db40508204ec88bef516fd2c397a51ecc Mon Sep 17 00:00:00 2001
From: buengese
Date: Sun, 12 Jun 2022 21:35:26 +0200
Subject: [PATCH 029/560] uptobox: fix root path handling - fixes #5903
---
backend/uptobox/uptobox.go | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/backend/uptobox/uptobox.go b/backend/uptobox/uptobox.go
index 94ef96fd90dc7..5ffd4baf16905 100644
--- a/backend/uptobox/uptobox.go
+++ b/backend/uptobox/uptobox.go
@@ -163,7 +163,7 @@ func (f *Fs) splitPathFull(pth string) (string, string) {
}
// splitPath is modified splitPath version that doesn't include the seperator
-// in the base part
+// in the base path
func (f *Fs) splitPath(pth string) (string, string) {
// chop of any leading or trailing '/'
pth = strings.Trim(pth, "/")
@@ -201,7 +201,11 @@ func NewFs(ctx context.Context, name string, root string, config configmap.Mappe
opt: *opt,
pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant), pacer.AttackConstant(attackConstant))),
}
- f.root = root
+ if root == "/" || root == "." {
+ f.root = ""
+ } else {
+ f.root = root
+ }
f.features = (&fs.Features{
DuplicateFiles: true,
CanHaveEmptyDirectories: true,
From 0f41e91d411fdfbd517a2c2a8f76bdaeb9b0ae47 Mon Sep 17 00:00:00 2001
From: Roberto Ricci
Date: Tue, 9 Nov 2021 16:09:12 +0100
Subject: [PATCH 030/560] cmd/ncdu: display correct path in delete confirmation
dialog
If the remote on the command line is "remote:subdir", when
deleting "filename", the confirmation message shows the path
"remote:subdirfilename".
Using fspath.JoinRootPath() fixes this. Also use this function
and fs.ConfigString() in other parts of the file, since they
are more appropriate.
---
cmd/ncdu/ncdu.go | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go
index 6359184b58130..4ef08f3468a3d 100644
--- a/cmd/ncdu/ncdu.go
+++ b/cmd/ncdu/ncdu.go
@@ -19,6 +19,7 @@ import (
"github.com/rclone/rclone/cmd"
"github.com/rclone/rclone/cmd/ncdu/scan"
"github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fs/fspath"
"github.com/rclone/rclone/fs/operations"
"github.com/spf13/cobra"
)
@@ -529,7 +530,7 @@ func (u *UI) delete() {
}
u.popupBox([]string{
"Delete this file?",
- u.fsName + dirEntry.String()})
+ fspath.JoinRootPath(u.fsName, dirEntry.String())})
} else {
u.boxMenuHandler = func(f fs.Fs, p string, o int) (string, error) {
if o != 1 {
@@ -548,7 +549,7 @@ func (u *UI) delete() {
u.popupBox([]string{
"Purge this directory?",
"ALL files in it will be deleted",
- u.fsName + dirEntry.String()})
+ fspath.JoinRootPath(u.fsName, dirEntry.String())})
}
}
@@ -659,7 +660,7 @@ func (u *UI) sortCurrentDir() {
func (u *UI) setCurrentDir(d *scan.Dir) {
u.d = d
u.entries = d.Entries()
- u.path = path.Join(u.fsName, d.Path())
+ u.path = fspath.JoinRootPath(u.fsName, d.Path())
u.sortCurrentDir()
}
@@ -742,7 +743,7 @@ func NewUI(f fs.Fs) *UI {
f: f,
path: "Waiting for root...",
dirListHeight: 20, // updated in Draw
- fsName: f.Name() + ":" + f.Root(),
+ fsName: fs.ConfigString(f),
showGraph: true,
showCounts: false,
showDirAverageSize: false,
From 5006ede266074f223243ab3f6f6adf68d817ab7c Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 8 Jun 2022 23:02:01 +0200
Subject: [PATCH 031/560] mailru: use variable type int instead of
time.Duration for keeping number of seconds
Fixes problem reported by staticcheck lint tool (v0.3.2):
Poorly chosen name for variable of type time.Duration (ST1011).
---
backend/mailru/mailru.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/mailru/mailru.go b/backend/mailru/mailru.go
index c1a51cf4af1f7..033f1d16d8a61 100644
--- a/backend/mailru/mailru.go
+++ b/backend/mailru/mailru.go
@@ -2277,7 +2277,7 @@ type serverPool struct {
pool pendingServerMap
mu sync.Mutex
path string
- expirySec time.Duration
+ expirySec int
fs *Fs
}
@@ -2384,7 +2384,7 @@ func (p *serverPool) addServer(url string, now time.Time) {
p.mu.Lock()
defer p.mu.Unlock()
- expiry := now.Add(p.expirySec * time.Second)
+ expiry := now.Add(time.Duration(p.expirySec) * time.Second)
expiryStr := []byte("-")
if p.fs.ci.LogLevel >= fs.LogLevelInfo {
From 70d1d8d760f93e3fe08e78e9e2441335cef2eade Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 12 Jun 2022 12:37:00 +0200
Subject: [PATCH 032/560] mount: replace deprecated fuse.ENOSYS with
syscall.ENOSYS
---
cmd/mount/dir.go | 3 ++-
cmd/mount/file.go | 9 +++++----
cmd/mount/fs.go | 2 +-
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/cmd/mount/dir.go b/cmd/mount/dir.go
index 2acb0c48e5cba..c49373d59892a 100644
--- a/cmd/mount/dir.go
+++ b/cmd/mount/dir.go
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
+ "syscall"
"time"
"bazil.org/fuse"
@@ -237,7 +238,7 @@ var _ fusefs.NodeLinker = (*Dir)(nil)
// existing Node. Receiver must be a directory.
func (d *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fusefs.Node) (newNode fusefs.Node, err error) {
defer log.Trace(d, "req=%v, old=%v", req, old)("new=%v, err=%v", &newNode, &err)
- return nil, fuse.ENOSYS
+ return nil, syscall.ENOSYS
}
// Check interface satisfied
diff --git a/cmd/mount/file.go b/cmd/mount/file.go
index 9e8b977db07d5..434ed753fe12f 100644
--- a/cmd/mount/file.go
+++ b/cmd/mount/file.go
@@ -5,6 +5,7 @@ package mount
import (
"context"
+ "syscall"
"time"
"bazil.org/fuse"
@@ -98,14 +99,14 @@ func (f *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) {
//
// If there is no xattr by that name, returns fuse.ErrNoXattr.
func (f *File) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
- return fuse.ENOSYS // we never implement this
+ return syscall.ENOSYS // we never implement this
}
var _ fusefs.NodeGetxattrer = (*File)(nil)
// Listxattr lists the extended attributes recorded for the node.
func (f *File) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
- return fuse.ENOSYS // we never implement this
+ return syscall.ENOSYS // we never implement this
}
var _ fusefs.NodeListxattrer = (*File)(nil)
@@ -113,7 +114,7 @@ var _ fusefs.NodeListxattrer = (*File)(nil)
// Setxattr sets an extended attribute with the given name and
// value for the node.
func (f *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
- return fuse.ENOSYS // we never implement this
+ return syscall.ENOSYS // we never implement this
}
var _ fusefs.NodeSetxattrer = (*File)(nil)
@@ -122,7 +123,7 @@ var _ fusefs.NodeSetxattrer = (*File)(nil)
//
// If there is no xattr by that name, returns fuse.ErrNoXattr.
func (f *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
- return fuse.ENOSYS // we never implement this
+ return syscall.ENOSYS // we never implement this
}
var _ fusefs.NodeRemovexattrer = (*File)(nil)
diff --git a/cmd/mount/fs.go b/cmd/mount/fs.go
index 8e9dad7513a8a..43d37802765c3 100644
--- a/cmd/mount/fs.go
+++ b/cmd/mount/fs.go
@@ -98,7 +98,7 @@ func translateError(err error) error {
case vfs.EROFS:
return fuse.Errno(syscall.EROFS)
case vfs.ENOSYS, fs.ErrorNotImplemented:
- return fuse.ENOSYS
+ return syscall.ENOSYS
case vfs.EINVAL:
return fuse.Errno(syscall.EINVAL)
}
From afa30abd33d031076ec8ac6354d6d2dee7ee730a Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 12 Jun 2022 12:24:45 +0200
Subject: [PATCH 033/560] mount: remove legacy OS X remnants
---
cmd/mount/dir.go | 1 -
cmd/mount/file.go | 1 -
cmd/mount/mount.go | 7 -------
3 files changed, 9 deletions(-)
diff --git a/cmd/mount/dir.go b/cmd/mount/dir.go
index c49373d59892a..47ec4bf2da5d2 100644
--- a/cmd/mount/dir.go
+++ b/cmd/mount/dir.go
@@ -39,7 +39,6 @@ func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) (err error) {
a.Atime = modTime
a.Mtime = modTime
a.Ctime = modTime
- a.Crtime = modTime
// FIXME include Valid so get some caching?
// FIXME fs.Debugf(d.path, "Dir.Attr %+v", a)
return nil
diff --git a/cmd/mount/file.go b/cmd/mount/file.go
index 434ed753fe12f..1a0ad6c2860f5 100644
--- a/cmd/mount/file.go
+++ b/cmd/mount/file.go
@@ -37,7 +37,6 @@ func (f *File) Attr(ctx context.Context, a *fuse.Attr) (err error) {
a.Atime = modTime
a.Mtime = modTime
a.Ctime = modTime
- a.Crtime = modTime
a.Blocks = Blocks
return nil
}
diff --git a/cmd/mount/mount.go b/cmd/mount/mount.go
index d6fbf1a92e7a8..db0f23bd38245 100644
--- a/cmd/mount/mount.go
+++ b/cmd/mount/mount.go
@@ -27,7 +27,6 @@ func mountOptions(VFS *vfs.VFS, device string, opt *mountlib.Options) (options [
fuse.MaxReadahead(uint32(opt.MaxReadAhead)),
fuse.Subtype("rclone"),
fuse.FSName(device),
- fuse.VolumeName(opt.VolumeName),
// Options from benchmarking in the fuse module
//fuse.MaxReadahead(64 * 1024 * 1024),
@@ -105,12 +104,6 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
errChan <- err
}()
- // check if the mount process has an error to report
- <-c.Ready
- if err := c.MountError; err != nil {
- return nil, nil, err
- }
-
unmount := func() error {
// Shutdown the VFS
filesys.VFS.Shutdown()
From 74bd7f3381ed4322750a88e7e26e413b69e307da Mon Sep 17 00:00:00 2001
From: buengese
Date: Mon, 13 Jun 2022 20:11:31 +0200
Subject: [PATCH 034/560] pcloud: fix about with no free space left
---
backend/pcloud/pcloud.go | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/backend/pcloud/pcloud.go b/backend/pcloud/pcloud.go
index 82f9f42f53dbf..cb470599c9b9b 100644
--- a/backend/pcloud/pcloud.go
+++ b/backend/pcloud/pcloud.go
@@ -908,10 +908,14 @@ func (f *Fs) About(ctx context.Context) (usage *fs.Usage, err error) {
if err != nil {
return nil, err
}
+ free := q.Quota - q.UsedQuota
+ if free < 0 {
+ free = 0
+ }
usage = &fs.Usage{
- Total: fs.NewUsageValue(q.Quota), // quota of bytes that can be used
- Used: fs.NewUsageValue(q.UsedQuota), // bytes in use
- Free: fs.NewUsageValue(q.Quota - q.UsedQuota), // bytes which can be uploaded before reaching the quota
+ Total: fs.NewUsageValue(q.Quota), // quota of bytes that can be used
+ Used: fs.NewUsageValue(q.UsedQuota), // bytes in use
+ Free: fs.NewUsageValue(free), // bytes which can be uploaded before reaching the quota
}
return usage, nil
}
From ec117593f18c8abbf00a6a9a067f576726bc7224 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 8 Jun 2022 22:25:17 +0200
Subject: [PATCH 035/560] Fix lint issues reported by staticcheck
Used staticcheck 2022.1.2 (v0.3.2)
See: staticcheck.io
---
backend/amazonclouddrive/amazonclouddrive.go | 2 +-
backend/b2/b2.go | 15 +++-------
backend/box/box.go | 2 +-
backend/cache/storage_memory.go | 5 +---
backend/cache/storage_persistent.go | 5 +---
backend/combine/combine.go | 2 +-
backend/compress/compress.go | 2 +-
backend/drive/drive.go | 14 ++++-----
backend/filefabric/filefabric.go | 2 +-
.../googlecloudstorage/googlecloudstorage.go | 2 +-
backend/googlephotos/googlephotos_test.go | 2 +-
backend/googlephotos/pattern_test.go | 2 +-
backend/hdfs/object.go | 4 +--
backend/hubic/hubic.go | 2 +-
backend/jottacloud/jottacloud.go | 5 +---
backend/local/about_windows.go | 6 +++-
backend/mailru/mailru.go | 4 +--
backend/netstorage/netstorage.go | 30 +++++++++----------
backend/onedrive/api/types.go | 2 +-
backend/onedrive/onedrive.go | 15 ++++++----
backend/opendrive/opendrive.go | 9 ++----
backend/pcloud/api/types.go | 2 +-
backend/pcloud/pcloud.go | 2 +-
backend/qingstor/qingstor.go | 7 +----
backend/s3/s3.go | 6 ++--
backend/seafile/seafile.go | 10 +++----
backend/sharefile/sharefile.go | 4 +--
backend/sia/sia.go | 4 +--
backend/sugarsync/sugarsync.go | 2 +-
backend/swift/swift.go | 8 ++---
backend/uptobox/uptobox.go | 7 ++---
backend/webdav/webdav.go | 9 ++----
backend/yandex/api/types.go | 2 +-
backend/yandex/yandex.go | 13 +++-----
backend/zoho/zoho.go | 2 +-
cmd/cmd.go | 4 +--
cmd/cryptdecode/cryptdecode.go | 4 +--
cmd/selfupdate/selfupdate.go | 8 ++---
cmd/serve/docker/docker.go | 2 +-
cmd/serve/proxy/proxy.go | 4 +--
cmd/test/makefiles/makefiles.go | 1 -
fs/accounting/accounting.go | 2 +-
fs/accounting/stats_groups_test.go | 4 +--
fs/asyncreader/asyncreader.go | 4 +--
fs/config/configfile/configfile.go | 5 +---
fs/config/rc.go | 5 +---
fs/filter/filter.go | 2 +-
fs/fserrors/error.go | 2 +-
fs/fserrors/error_test.go | 2 +-
fs/fspath/path.go | 2 +-
fs/log/caller_hook.go | 2 +-
fs/open_options.go | 4 +--
fs/rc/rcserver/rcserver.go | 2 +-
fs/rc/webgui/plugins.go | 6 ++--
fstest/fstests/fstests.go | 5 ++--
lib/encoder/internal/gen/main.go | 8 ++---
lib/kv/internal_test.go | 2 --
lib/oauthutil/oauthutil.go | 3 +-
lib/pacer/tokens.go | 1 -
lib/version/version.go | 2 +-
vfs/dir.go | 2 +-
vfs/read.go | 2 +-
vfs/vfscache/downloaders/downloaders.go | 2 +-
vfs/vfscache/item.go | 4 +--
vfs/vfstest/write_non_unix.go | 5 +---
vfs/write.go | 4 +--
66 files changed, 130 insertions(+), 185 deletions(-)
diff --git a/backend/amazonclouddrive/amazonclouddrive.go b/backend/amazonclouddrive/amazonclouddrive.go
index 58a14427bc4f4..e5f5512ed2794 100644
--- a/backend/amazonclouddrive/amazonclouddrive.go
+++ b/backend/amazonclouddrive/amazonclouddrive.go
@@ -435,7 +435,7 @@ func (f *Fs) listAll(ctx context.Context, dirID string, title string, directorie
query += " AND kind:" + folderKind
} else if filesOnly {
query += " AND kind:" + fileKind
- } else {
+ //} else {
// FIXME none of these work
//query += " AND kind:(" + fileKind + " OR " + folderKind + ")"
//query += " AND (kind:" + fileKind + " OR kind:" + folderKind + ")"
diff --git a/backend/b2/b2.go b/backend/b2/b2.go
index ba3e03eb75895..322aa50a56efd 100644
--- a/backend/b2/b2.go
+++ b/backend/b2/b2.go
@@ -280,7 +280,7 @@ func (f *Fs) Root() string {
// String converts this Fs to a string
func (f *Fs) String() string {
if f.rootBucket == "" {
- return fmt.Sprintf("B2 root")
+ return "B2 root"
}
if f.rootDirectory == "" {
return fmt.Sprintf("B2 bucket %s", f.rootBucket)
@@ -1205,10 +1205,7 @@ func (f *Fs) purge(ctx context.Context, dir string, oldOnly bool) error {
}
}
var isUnfinishedUploadStale = func(timestamp api.Timestamp) bool {
- if time.Since(time.Time(timestamp)).Hours() > 24 {
- return true
- }
- return false
+ return time.Since(time.Time(timestamp)).Hours() > 24
}
// Delete Config.Transfers in parallel
@@ -1485,13 +1482,9 @@ func (o *Object) Size() int64 {
//
// Remove unverified prefix - see https://www.backblaze.com/b2/docs/uploading.html
// Some tools (e.g. Cyberduck) use this
-func cleanSHA1(sha1 string) (out string) {
- out = strings.ToLower(sha1)
+func cleanSHA1(sha1 string) string {
const unverified = "unverified:"
- if strings.HasPrefix(out, unverified) {
- out = out[len(unverified):]
- }
- return out
+ return strings.TrimPrefix(strings.ToLower(sha1), unverified)
}
// decodeMetaDataRaw sets the metadata from the data passed in
diff --git a/backend/box/box.go b/backend/box/box.go
index bde259cead077..55d8a40303c08 100644
--- a/backend/box/box.go
+++ b/backend/box/box.go
@@ -897,7 +897,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
srcPath := srcObj.fs.rootSlash() + srcObj.remote
dstPath := f.rootSlash() + remote
- if strings.ToLower(srcPath) == strings.ToLower(dstPath) {
+ if strings.EqualFold(srcPath, dstPath) {
return nil, fmt.Errorf("can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
}
diff --git a/backend/cache/storage_memory.go b/backend/cache/storage_memory.go
index 8e8a360fce5ef..25ac3bb6258f2 100644
--- a/backend/cache/storage_memory.go
+++ b/backend/cache/storage_memory.go
@@ -76,10 +76,7 @@ func (m *Memory) CleanChunksByAge(chunkAge time.Duration) {
// CleanChunksByNeed will cleanup chunks after the FS passes a specific chunk
func (m *Memory) CleanChunksByNeed(offset int64) {
- var items map[string]cache.Item
-
- items = m.db.Items()
- for key := range items {
+ for key := range m.db.Items() {
sepIdx := strings.LastIndex(key, "-")
keyOffset, err := strconv.ParseInt(key[sepIdx+1:], 10, 64)
if err != nil {
diff --git a/backend/cache/storage_persistent.go b/backend/cache/storage_persistent.go
index 89af35f87342c..0de9a39633396 100644
--- a/backend/cache/storage_persistent.go
+++ b/backend/cache/storage_persistent.go
@@ -456,10 +456,7 @@ func (b *Persistent) HasEntry(remote string) bool {
return fmt.Errorf("couldn't find object (%v)", remote)
})
- if err == nil {
- return true
- }
- return false
+ return err == nil
}
// HasChunk confirms the existence of a single chunk of an object
diff --git a/backend/combine/combine.go b/backend/combine/combine.go
index c3c5ff38a792d..934ed76f44ada 100644
--- a/backend/combine/combine.go
+++ b/backend/combine/combine.go
@@ -195,7 +195,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (outFs fs
if remote == "" {
return fmt.Errorf("empty remote in upstream definition %q", upstream)
}
- if strings.IndexRune(dir, '/') >= 0 {
+ if strings.ContainsRune(dir, '/') {
return fmt.Errorf("dirs can't contain / (yet): %q", dir)
}
u, err := f.newUpstream(gCtx, dir, remote)
diff --git a/backend/compress/compress.go b/backend/compress/compress.go
index 9656fb57308be..6d3397dfb0019 100644
--- a/backend/compress/compress.go
+++ b/backend/compress/compress.go
@@ -53,7 +53,7 @@ const (
Gzip = 2
)
-var nameRegexp = regexp.MustCompile("^(.+?)\\.([A-Za-z0-9-_]{11})$")
+var nameRegexp = regexp.MustCompile(`^(.+?)\.([A-Za-z0-9-_]{11})$`)
// Register with Fs
func init() {
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index 9aeeec5a04153..c5f5827f7b65b 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -2020,7 +2020,7 @@ func splitID(compositeID string) (actualID, shortcutID string) {
// isShortcutID returns true if compositeID refers to a shortcut
func isShortcutID(compositeID string) bool {
- return strings.IndexRune(compositeID, shortcutSeparator) >= 0
+ return strings.ContainsRune(compositeID, shortcutSeparator)
}
// actualID returns an actual ID from a composite ID
@@ -3327,7 +3327,7 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
out["service_account_file"] = f.opt.ServiceAccountFile
}
if _, ok := opt["chunk_size"]; ok {
- out["chunk_size"] = fmt.Sprintf("%s", f.opt.ChunkSize)
+ out["chunk_size"] = f.opt.ChunkSize.String()
}
return out, nil
case "set":
@@ -3344,11 +3344,11 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
}
if chunkSize, ok := opt["chunk_size"]; ok {
chunkSizeMap := make(map[string]string)
- chunkSizeMap["previous"] = fmt.Sprintf("%s", f.opt.ChunkSize)
+ chunkSizeMap["previous"] = f.opt.ChunkSize.String()
if err = f.changeChunkSize(chunkSize); err != nil {
return out, err
}
- chunkSizeString := fmt.Sprintf("%s", f.opt.ChunkSize)
+ chunkSizeString := f.opt.ChunkSize.String()
f.m.Set("chunk_size", chunkSizeString)
chunkSizeMap["current"] = chunkSizeString
out["chunk_size"] = chunkSizeMap
@@ -3392,13 +3392,13 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
names[name] = struct{}{}
lines = append(lines, "")
lines = append(lines, fmt.Sprintf("[%s]", name))
- lines = append(lines, fmt.Sprintf("type = alias"))
+ lines = append(lines, "type = alias")
lines = append(lines, fmt.Sprintf("remote = %s,team_drive=%s,root_folder_id=:", f.name, drive.Id))
upstreams = append(upstreams, fmt.Sprintf(`"%s=%s:"`, name, name))
}
lines = append(lines, "")
- lines = append(lines, fmt.Sprintf("[AllDrives]"))
- lines = append(lines, fmt.Sprintf("type = combine"))
+ lines = append(lines, "[AllDrives]")
+ lines = append(lines, "type = combine")
lines = append(lines, fmt.Sprintf("upstreams = %s", strings.Join(upstreams, " ")))
return lines, nil
}
diff --git a/backend/filefabric/filefabric.go b/backend/filefabric/filefabric.go
index 7f49579b479db..a70e1f7aea2c3 100644
--- a/backend/filefabric/filefabric.go
+++ b/backend/filefabric/filefabric.go
@@ -490,7 +490,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
// Root is a dir - cache its ID
f.dirCache.Put(f.root, info.ID)
}
- } else {
+ //} else {
// Root is not found so a directory
}
}
diff --git a/backend/googlecloudstorage/googlecloudstorage.go b/backend/googlecloudstorage/googlecloudstorage.go
index 2f8a8856278e5..e010dfd88bdd7 100644
--- a/backend/googlecloudstorage/googlecloudstorage.go
+++ b/backend/googlecloudstorage/googlecloudstorage.go
@@ -391,7 +391,7 @@ func (f *Fs) Root() string {
// String converts this Fs to a string
func (f *Fs) String() string {
if f.rootBucket == "" {
- return fmt.Sprintf("GCS root")
+ return "GCS root"
}
if f.rootDirectory == "" {
return fmt.Sprintf("GCS bucket %s", f.rootBucket)
diff --git a/backend/googlephotos/googlephotos_test.go b/backend/googlephotos/googlephotos_test.go
index c65a02700e2d5..11cb28e1bf271 100644
--- a/backend/googlephotos/googlephotos_test.go
+++ b/backend/googlephotos/googlephotos_test.go
@@ -37,7 +37,7 @@ func TestIntegration(t *testing.T) {
}
f, err := fs.NewFs(ctx, *fstest.RemoteName)
if err == fs.ErrorNotFoundInConfigFile {
- t.Skip(fmt.Sprintf("Couldn't create google photos backend - skipping tests: %v", err))
+ t.Skipf("Couldn't create google photos backend - skipping tests: %v", err)
}
require.NoError(t, err)
diff --git a/backend/googlephotos/pattern_test.go b/backend/googlephotos/pattern_test.go
index 6d58e5475e036..b446615c22120 100644
--- a/backend/googlephotos/pattern_test.go
+++ b/backend/googlephotos/pattern_test.go
@@ -50,7 +50,7 @@ func (f *testLister) listAlbums(ctx context.Context, shared bool) (all *albums,
// mock listUploads for testing
func (f *testLister) listUploads(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
- entries, _ = f.uploaded[dir]
+ entries = f.uploaded[dir]
return entries, nil
}
diff --git a/backend/hdfs/object.go b/backend/hdfs/object.go
index d7b94afbd3bae..976bc7f02ea9c 100644
--- a/backend/hdfs/object.go
+++ b/backend/hdfs/object.go
@@ -115,7 +115,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
return err
}
- info, err := o.fs.client.Stat(realpath)
+ _, err = o.fs.client.Stat(realpath)
if err == nil {
err = o.fs.client.Remove(realpath)
if err != nil {
@@ -147,7 +147,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
return err
}
- info, err = o.fs.client.Stat(realpath)
+ info, err := o.fs.client.Stat(realpath)
if err != nil {
return err
}
diff --git a/backend/hubic/hubic.go b/backend/hubic/hubic.go
index c655c038efa4d..5c284194eff0d 100644
--- a/backend/hubic/hubic.go
+++ b/backend/hubic/hubic.go
@@ -138,7 +138,7 @@ func (f *Fs) getCredentials(ctx context.Context) (err error) {
return err
}
f.expires = expires
- fs.Debugf(f, "Got swift credentials (expiry %v in %v)", f.expires, f.expires.Sub(time.Now()))
+ fs.Debugf(f, "Got swift credentials (expiry %v in %v)", f.expires, time.Until(f.expires))
return nil
}
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index 12fe1d255cb6d..735b5181fb080 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -1252,10 +1252,7 @@ func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options .
func (f *Fs) mkParentDir(ctx context.Context, dirPath string) error {
// defer log.Trace(dirPath, "")("")
// chop off trailing / if it exists
- if strings.HasSuffix(dirPath, "/") {
- dirPath = dirPath[:len(dirPath)-1]
- }
- parent := path.Dir(dirPath)
+ parent := path.Dir(strings.TrimSuffix(dirPath, "/"))
if parent == "." {
parent = ""
}
diff --git a/backend/local/about_windows.go b/backend/local/about_windows.go
index d8daed7644fa2..f4fba6195a1ad 100644
--- a/backend/local/about_windows.go
+++ b/backend/local/about_windows.go
@@ -17,8 +17,12 @@ var getFreeDiskSpace = syscall.NewLazyDLL("kernel32.dll").NewProc("GetDiskFreeSp
// About gets quota information
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
var available, total, free int64
+ root, e := syscall.UTF16PtrFromString(f.root)
+ if e != nil {
+ return nil, fmt.Errorf("failed to read disk usage: %w", e)
+ }
_, _, e1 := getFreeDiskSpace.Call(
- uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(f.root))),
+ uintptr(unsafe.Pointer(root)),
uintptr(unsafe.Pointer(&available)), // lpFreeBytesAvailable - for this user
uintptr(unsafe.Pointer(&total)), // lpTotalNumberOfBytes
uintptr(unsafe.Pointer(&free)), // lpTotalNumberOfFreeBytes
diff --git a/backend/mailru/mailru.go b/backend/mailru/mailru.go
index 033f1d16d8a61..24693c67cfcb8 100644
--- a/backend/mailru/mailru.go
+++ b/backend/mailru/mailru.go
@@ -1723,7 +1723,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
return err
}
- if bytes.Compare(fileHash, newHash) != 0 {
+ if !bytes.Equal(fileHash, newHash) {
if o.fs.opt.CheckHash {
return mrhash.ErrorInvalidHash
}
@@ -2262,7 +2262,7 @@ func (e *endHandler) handle(err error) error {
}
newHash := e.hasher.Sum(nil)
- if bytes.Compare(o.mrHash, newHash) == 0 {
+ if bytes.Equal(o.mrHash, newHash) {
return io.EOF
}
if o.fs.opt.CheckHash {
diff --git a/backend/netstorage/netstorage.go b/backend/netstorage/netstorage.go
index cbb2b04888b77..313679e272f8d 100755
--- a/backend/netstorage/netstorage.go
+++ b/backend/netstorage/netstorage.go
@@ -903,22 +903,20 @@ func (f *Fs) netStorageStatRequest(ctx context.Context, URL string, directory bo
files = statResp.Files
f.setStatCache(URL, files)
}
- if files != nil {
- // Multiple objects can be returned with the "slash=both" option,
- // when file/symlink/directory has the same name
- for i := range files {
- if files[i].Type == "symlink" {
- // Add .rclonelink suffix to allow local backend code to convert to a symlink.
- files[i].Name += ".rclonelink"
- fs.Infof(nil, "Converting a symlink to the rclonelink on the stat request %s", files[i].Name)
- }
- entrywanted := (directory && files[i].Type == "dir") ||
- (!directory && files[i].Type != "dir")
- if entrywanted {
- filestamp := files[0]
- files[0] = files[i]
- files[i] = filestamp
- }
+ // Multiple objects can be returned with the "slash=both" option,
+ // when file/symlink/directory has the same name
+ for i := range files {
+ if files[i].Type == "symlink" {
+ // Add .rclonelink suffix to allow local backend code to convert to a symlink.
+ files[i].Name += ".rclonelink"
+ fs.Infof(nil, "Converting a symlink to the rclonelink on the stat request %s", files[i].Name)
+ }
+ entrywanted := (directory && files[i].Type == "dir") ||
+ (!directory && files[i].Type != "dir")
+ if entrywanted {
+ filestamp := files[0]
+ files[0] = files[i]
+ files[i] = filestamp
}
}
return files, nil
diff --git a/backend/onedrive/api/types.go b/backend/onedrive/api/types.go
index 394403b0e58ac..236c1c68218b4 100644
--- a/backend/onedrive/api/types.go
+++ b/backend/onedrive/api/types.go
@@ -292,7 +292,7 @@ type AsyncOperationStatus struct {
func (i *Item) GetID() string {
if i.IsRemote() && i.RemoteItem.ID != "" {
return i.RemoteItem.ParentReference.DriveID + "#" + i.RemoteItem.ID
- } else if i.ParentReference != nil && strings.Index(i.ID, "#") == -1 {
+ } else if i.ParentReference != nil && !strings.Contains(i.ID, "#") {
return i.ParentReference.DriveID + "#" + i.ID
}
return i.ID
diff --git a/backend/onedrive/onedrive.go b/backend/onedrive/onedrive.go
index 132ed77e66a9c..a077843ed3f18 100644
--- a/backend/onedrive/onedrive.go
+++ b/backend/onedrive/onedrive.go
@@ -660,7 +660,7 @@ func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, err
}
}
case 401:
- if len(resp.Header["Www-Authenticate"]) == 1 && strings.Index(resp.Header["Www-Authenticate"][0], "expired_token") >= 0 {
+ if len(resp.Header["Www-Authenticate"]) == 1 && strings.Contains(resp.Header["Www-Authenticate"][0], "expired_token") {
retry = true
fs.Debugf(nil, "Should retry: %v", err)
} else if err != nil && strings.Contains(err.Error(), "Unable to initialize RPS") {
@@ -716,8 +716,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.It
firstSlashIndex := strings.IndexRune(path, '/')
if f.driveType != driveTypePersonal || firstSlashIndex == -1 {
- var opts rest.Opts
- opts = f.newOptsCallWithPath(ctx, path, "GET", "")
+ opts := f.newOptsCallWithPath(ctx, path, "GET", "")
opts.Path = strings.TrimSuffix(opts.Path, ":")
err = f.pacer.Call(func() (bool, error) {
resp, err = f.srv.CallJSON(ctx, &opts, nil, &info)
@@ -1287,7 +1286,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
if srcObj.fs == f {
srcPath := srcObj.rootPath()
dstPath := f.rootPath(remote)
- if strings.ToLower(srcPath) == strings.ToLower(dstPath) {
+ if strings.EqualFold(srcPath, dstPath) {
return nil, fmt.Errorf("can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
}
}
@@ -2185,7 +2184,7 @@ func (o *Object) ID() string {
// Such a normalized ID can come from (*Item).GetID()
func (f *Fs) parseNormalizedID(ID string) (string, string, string) {
rootURL := graphAPIEndpoint[f.opt.Region] + "/v1.0/drives"
- if strings.Index(ID, "#") >= 0 {
+ if strings.Contains(ID, "#") {
s := strings.Split(ID, "#")
return s[1], s[0], rootURL
}
@@ -2359,6 +2358,9 @@ func (f *Fs) ChangeNotify(ctx context.Context, notifyFunc func(string, fs.EntryT
func (f *Fs) changeNotifyStartPageToken(ctx context.Context) (nextDeltaToken string, err error) {
delta, err := f.changeNotifyNextChange(ctx, "latest")
+ if err != nil {
+ return
+ }
parsedURL, err := url.Parse(delta.DeltaLink)
if err != nil {
return
@@ -2388,6 +2390,9 @@ func (f *Fs) buildDriveDeltaOpts(token string) rest.Opts {
func (f *Fs) changeNotifyRunner(ctx context.Context, notifyFunc func(string, fs.EntryType), deltaToken string) (nextDeltaToken string, err error) {
delta, err := f.changeNotifyNextChange(ctx, deltaToken)
+ if err != nil {
+ return
+ }
parsedURL, err := url.Parse(delta.DeltaLink)
if err != nil {
return
diff --git a/backend/opendrive/opendrive.go b/backend/opendrive/opendrive.go
index 96f29aa33de43..6e5967e626c1c 100644
--- a/backend/opendrive/opendrive.go
+++ b/backend/opendrive/opendrive.go
@@ -361,7 +361,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
srcPath := srcObj.fs.rootSlash() + srcObj.remote
dstPath := f.rootSlash() + remote
- if strings.ToLower(srcPath) == strings.ToLower(dstPath) {
+ if strings.EqualFold(srcPath, dstPath) {
return nil, fmt.Errorf("Can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
}
@@ -588,9 +588,6 @@ func (f *Fs) readMetaDataForFolderID(ctx context.Context, id string) (info *Fold
if err != nil {
return nil, err
}
- if resp != nil {
- }
-
return info, err
}
@@ -611,13 +608,13 @@ func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options .
return nil, err
}
- if "" == o.id {
+ if o.id == "" {
// Attempt to read ID, ignore error
// FIXME is this correct?
_ = o.readMetaData(ctx)
}
- if "" == o.id {
+ if o.id == "" {
// We need to create an ID for this file
var resp *http.Response
response := createFileResponse{}
diff --git a/backend/pcloud/api/types.go b/backend/pcloud/api/types.go
index 974d9f5f36fbd..5ae2b6b6bb494 100644
--- a/backend/pcloud/api/types.go
+++ b/backend/pcloud/api/types.go
@@ -136,7 +136,7 @@ func (g *GetFileLinkResult) IsValid() bool {
if len(g.Hosts) == 0 {
return false
}
- return time.Time(g.Expires).Sub(time.Now()) > 30*time.Second
+ return time.Until(time.Time(g.Expires)) > 30*time.Second
}
// URL returns a URL from the Path and Hosts. Check with IsValid
diff --git a/backend/pcloud/pcloud.go b/backend/pcloud/pcloud.go
index cb470599c9b9b..a73593b4059da 100644
--- a/backend/pcloud/pcloud.go
+++ b/backend/pcloud/pcloud.go
@@ -227,7 +227,7 @@ func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, err
}
}
- if resp != nil && resp.StatusCode == 401 && len(resp.Header["Www-Authenticate"]) == 1 && strings.Index(resp.Header["Www-Authenticate"][0], "expired_token") >= 0 {
+ if resp != nil && resp.StatusCode == 401 && len(resp.Header["Www-Authenticate"]) == 1 && strings.Contains(resp.Header["Www-Authenticate"][0], "expired_token") {
doRetry = true
fs.Debugf(nil, "Should retry: %v", err)
}
diff --git a/backend/qingstor/qingstor.go b/backend/qingstor/qingstor.go
index d4053e250ffca..5f5134099be09 100644
--- a/backend/qingstor/qingstor.go
+++ b/backend/qingstor/qingstor.go
@@ -573,9 +573,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
if addBucket {
remote = path.Join(bucket, remote)
}
- if strings.HasSuffix(remote, "/") {
- remote = remote[:len(remote)-1]
- }
+ remote = strings.TrimSuffix(remote, "/")
err = fn(remote, &qs.KeyType{Key: &remote}, true)
if err != nil {
return err
@@ -775,8 +773,6 @@ func (f *Fs) makeBucket(ctx context.Context, bucket string) error {
retries++
wasDeleted = true
continue
- default:
- break
}
break
}
@@ -854,7 +850,6 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
continue
default:
err = e
- break
}
}
} else {
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index ea04ed653a2a4..0a7b216f488ea 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -1976,7 +1976,7 @@ func (f *Fs) Root() string {
// String converts this Fs to a string
func (f *Fs) String() string {
if f.rootBucket == "" {
- return fmt.Sprintf("S3 root")
+ return "S3 root"
}
if f.rootDirectory == "" {
return fmt.Sprintf("S3 bucket %s", f.rootBucket)
@@ -2669,9 +2669,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
if addBucket {
remote = path.Join(bucket, remote)
}
- if strings.HasSuffix(remote, "/") {
- remote = remote[:len(remote)-1]
- }
+ remote = strings.TrimSuffix(remote, "/")
err = fn(remote, &s3.Object{Key: &remote}, true)
if err != nil {
return err
diff --git a/backend/seafile/seafile.go b/backend/seafile/seafile.go
index 76f9843abca50..15a7086411484 100644
--- a/backend/seafile/seafile.go
+++ b/backend/seafile/seafile.go
@@ -453,7 +453,7 @@ func (f *Fs) Root() string {
// String converts this Fs to a string
func (f *Fs) String() string {
if f.libraryName == "" {
- return fmt.Sprintf("seafile root")
+ return "seafile root"
}
library := "library"
if f.encrypted {
@@ -984,9 +984,9 @@ func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration,
if err != nil {
return "", err
}
- if shareLinks != nil && len(shareLinks) > 0 {
+ if len(shareLinks) > 0 {
for _, shareLink := range shareLinks {
- if shareLink.IsExpired == false {
+ if !shareLink.IsExpired {
return shareLink.Link, nil
}
}
@@ -1053,7 +1053,7 @@ func (f *Fs) isLibraryInCache(libraryName string) bool {
return false
}
value, found := f.libraries.GetMaybe(librariesCacheKey)
- if found == false {
+ if !found {
return false
}
libraries := value.([]api.Library)
@@ -1130,7 +1130,7 @@ func (f *Fs) mkLibrary(ctx context.Context, libraryName, password string) error
}
// Stores the library details into the cache
value, found := f.libraries.GetMaybe(librariesCacheKey)
- if found == false {
+ if !found {
// Don't update the cache at that point
return nil
}
diff --git a/backend/sharefile/sharefile.go b/backend/sharefile/sharefile.go
index 69da61cb9cef3..e6add47c2e6ac 100644
--- a/backend/sharefile/sharefile.go
+++ b/backend/sharefile/sharefile.go
@@ -1077,7 +1077,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Obj
}
dstLeaf = f.opt.Enc.FromStandardName(dstLeaf)
- sameName := strings.ToLower(srcLeaf) == strings.ToLower(dstLeaf)
+ sameName := strings.EqualFold(srcLeaf, dstLeaf)
if sameName && srcParentID == dstParentID {
return nil, fmt.Errorf("copy: can't copy to a file in the same directory whose name only differs in case: %q vs %q", srcLeaf, dstLeaf)
}
@@ -1096,7 +1096,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Obj
directCopy = true
} else if err != nil {
return nil, fmt.Errorf("copy: failed to examine destination dir: %w", err)
- } else {
+ //} else {
// otherwise need to copy via a temporary directory
}
}
diff --git a/backend/sia/sia.go b/backend/sia/sia.go
index 2017332ce1b7a..2a8235638f282 100644
--- a/backend/sia/sia.go
+++ b/backend/sia/sia.go
@@ -423,9 +423,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
return nil, err
}
- if strings.HasSuffix(opt.APIURL, "/") {
- opt.APIURL = strings.TrimSuffix(opt.APIURL, "/")
- }
+ opt.APIURL = strings.TrimSuffix(opt.APIURL, "/")
// Parse the endpoint
u, err := url.Parse(opt.APIURL)
diff --git a/backend/sugarsync/sugarsync.go b/backend/sugarsync/sugarsync.go
index 4b57f4184a8a8..2041bdc05c9f8 100644
--- a/backend/sugarsync/sugarsync.go
+++ b/backend/sugarsync/sugarsync.go
@@ -872,7 +872,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
srcPath := srcObj.fs.rootSlash() + srcObj.remote
dstPath := f.rootSlash() + remote
- if strings.ToLower(srcPath) == strings.ToLower(dstPath) {
+ if strings.EqualFold(srcPath, dstPath) {
return nil, fmt.Errorf("can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
}
diff --git a/backend/swift/swift.go b/backend/swift/swift.go
index 5e3d96933d8f9..074e2daf7f9c6 100644
--- a/backend/swift/swift.go
+++ b/backend/swift/swift.go
@@ -268,7 +268,7 @@ func (f *Fs) Root() string {
// String converts this Fs to a string
func (f *Fs) String() string {
if f.rootContainer == "" {
- return fmt.Sprintf("Swift root")
+ return "Swift root"
}
if f.rootDirectory == "" {
return fmt.Sprintf("Swift container %s", f.rootContainer)
@@ -1271,7 +1271,7 @@ func (o *Object) getSegmentsLargeObject(ctx context.Context) (map[string][]strin
if _, ok := containerSegments[segmentContainer]; !ok {
containerSegments[segmentContainer] = make([]string, 0, len(segmentObjects))
}
- segments, _ := containerSegments[segmentContainer]
+ segments := containerSegments[segmentContainer]
segments = append(segments, segment.Name)
containerSegments[segmentContainer] = segments
}
@@ -1363,7 +1363,7 @@ func (o *Object) updateChunks(ctx context.Context, in0 io.Reader, headers swift.
return
}
fs.Debugf(o, "Delete segments when err raise %v", err)
- if segmentInfos == nil || len(segmentInfos) == 0 {
+ if len(segmentInfos) == 0 {
return
}
_ctx := context.Background()
@@ -1418,7 +1418,7 @@ func (o *Object) updateChunks(ctx context.Context, in0 io.Reader, headers swift.
}
func deleteChunks(ctx context.Context, o *Object, segmentsContainer string, segmentInfos []string) {
- if segmentInfos == nil || len(segmentInfos) == 0 {
+ if len(segmentInfos) == 0 {
return
}
for _, v := range segmentInfos {
diff --git a/backend/uptobox/uptobox.go b/backend/uptobox/uptobox.go
index 5ffd4baf16905..56c4f80a1ec4d 100644
--- a/backend/uptobox/uptobox.go
+++ b/backend/uptobox/uptobox.go
@@ -703,8 +703,7 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
}
// copy the old object and apply the changes
- var newObj Object
- newObj = *srcObj
+ newObj := *srcObj
newObj.remote = remote
newObj.fs = f
return &newObj, nil
@@ -759,7 +758,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
}
// check if the destination allready exists
dstPath := f.dirPath(dstRemote)
- dstInfo, err := f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 1})
+ _, err = f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 1})
if err == nil {
return fs.ErrorDirExists
}
@@ -772,7 +771,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
}
// find the destination parent dir
- dstInfo, err = f.readMetaDataForPath(ctx, dstBase, &api.MetadataRequestOptions{Limit: 1})
+ dstInfo, err := f.readMetaDataForPath(ctx, dstBase, &api.MetadataRequestOptions{Limit: 1})
if err != nil {
return fmt.Errorf("dirmove: failed to read destination: %w", err)
}
diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go
index 64c3e983a7fb0..7016b289cd042 100644
--- a/backend/webdav/webdav.go
+++ b/backend/webdav/webdav.go
@@ -716,9 +716,7 @@ func (f *Fs) listAll(ctx context.Context, dir string, directoriesOnly bool, file
subPath = f.opt.Enc.ToStandardPath(subPath)
}
remote := path.Join(dir, subPath)
- if strings.HasSuffix(remote, "/") {
- remote = remote[:len(remote)-1]
- }
+ remote = strings.TrimSuffix(remote, "/")
// the listing contains info about itself which we ignore
if remote == dir {
@@ -820,10 +818,7 @@ func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, opt
func (f *Fs) mkParentDir(ctx context.Context, dirPath string) (err error) {
// defer log.Trace(dirPath, "")("err=%v", &err)
// chop off trailing / if it exists
- if strings.HasSuffix(dirPath, "/") {
- dirPath = dirPath[:len(dirPath)-1]
- }
- parent := path.Dir(dirPath)
+ parent := path.Dir(strings.TrimSuffix(dirPath, "/"))
if parent == "." {
parent = ""
}
diff --git a/backend/yandex/api/types.go b/backend/yandex/api/types.go
index 978b1d00ce1d8..7e346b1ffe153 100644
--- a/backend/yandex/api/types.go
+++ b/backend/yandex/api/types.go
@@ -131,7 +131,7 @@ func (m *SortMode) String() string {
// UnmarshalJSON sort mode
func (m *SortMode) UnmarshalJSON(value []byte) error {
- if value == nil || len(value) == 0 {
+ if len(value) == 0 {
m.mode = ""
return nil
}
diff --git a/backend/yandex/yandex.go b/backend/yandex/yandex.go
index 265802cb5dda0..7eea7187f2223 100644
--- a/backend/yandex/yandex.go
+++ b/backend/yandex/yandex.go
@@ -468,7 +468,7 @@ func (f *Fs) CreateDir(ctx context.Context, path string) (err error) {
}
// If creating a directory with a : use (undocumented) disk: prefix
- if strings.IndexRune(path, ':') >= 0 {
+ if strings.ContainsRune(path, ':') {
path = "disk:" + path
}
opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(path))
@@ -506,10 +506,8 @@ func (f *Fs) mkDirs(ctx context.Context, path string) (err error) {
var mkdirpath = "/" //path separator /
for _, element := range dirs {
if element != "" {
- mkdirpath += element + "/" //path separator /
- if err = f.CreateDir(ctx, mkdirpath); err != nil {
- // ignore errors while creating dirs
- }
+ mkdirpath += element + "/" //path separator /
+ _ = f.CreateDir(ctx, mkdirpath) // ignore errors while creating dirs
}
}
}
@@ -522,10 +520,7 @@ func (f *Fs) mkDirs(ctx context.Context, path string) (err error) {
func (f *Fs) mkParentDirs(ctx context.Context, resPath string) error {
// defer log.Trace(dirPath, "")("")
// chop off trailing / if it exists
- if strings.HasSuffix(resPath, "/") {
- resPath = resPath[:len(resPath)-1]
- }
- parent := path.Dir(resPath)
+ parent := path.Dir(strings.TrimSuffix(resPath, "/"))
if parent == "." {
parent = ""
}
diff --git a/backend/zoho/zoho.go b/backend/zoho/zoho.go
index e0082cacd44d8..6175be3c5f327 100644
--- a/backend/zoho/zoho.go
+++ b/backend/zoho/zoho.go
@@ -287,7 +287,7 @@ func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, err
}
authRetry := false
- if resp != nil && resp.StatusCode == 401 && len(resp.Header["Www-Authenticate"]) == 1 && strings.Index(resp.Header["Www-Authenticate"][0], "expired_token") >= 0 {
+ if resp != nil && resp.StatusCode == 401 && len(resp.Header["Www-Authenticate"]) == 1 && strings.Contains(resp.Header["Www-Authenticate"][0], "expired_token") {
authRetry = true
fs.Debugf(nil, "Should retry: %v", err)
}
diff --git a/cmd/cmd.go b/cmd/cmd.go
index f2aea1fdf4714..82ae1ad838712 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -273,7 +273,7 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
break
}
if retryAfter := accounting.GlobalStats().RetryAfter(); !retryAfter.IsZero() {
- d := retryAfter.Sub(time.Now())
+ d := time.Until(retryAfter)
if d > 0 {
fs.Logf(nil, "Received retry after error - sleeping until %s (%v)", retryAfter.Format(time.RFC3339Nano), d)
time.Sleep(d)
@@ -458,7 +458,7 @@ func initConfig() {
})
}
- if m, _ := regexp.MatchString("^(bits|bytes)$", *dataRateUnit); m == false {
+ if m, _ := regexp.MatchString("^(bits|bytes)$", *dataRateUnit); !m {
fs.Errorf(nil, "Invalid unit passed to --stats-unit. Defaulting to bytes.")
ci.DataRateUnit = "bytes"
} else {
diff --git a/cmd/cryptdecode/cryptdecode.go b/cmd/cryptdecode/cryptdecode.go
index c8859ebce7277..bdfd3e22127bd 100644
--- a/cmd/cryptdecode/cryptdecode.go
+++ b/cmd/cryptdecode/cryptdecode.go
@@ -75,7 +75,7 @@ func cryptDecode(cipher *crypt.Cipher, args []string) error {
}
}
- fmt.Printf(output)
+ fmt.Print(output)
return nil
}
@@ -89,7 +89,7 @@ func cryptEncode(cipher *crypt.Cipher, args []string) error {
output += fmt.Sprintln(fileName, "\t", encryptedFileName)
}
- fmt.Printf(output)
+ fmt.Print(output)
return nil
}
diff --git a/cmd/selfupdate/selfupdate.go b/cmd/selfupdate/selfupdate.go
index f15bce83ce7dd..5bfd1cb667067 100644
--- a/cmd/selfupdate/selfupdate.go
+++ b/cmd/selfupdate/selfupdate.go
@@ -200,9 +200,7 @@ func InstallUpdate(ctx context.Context, opt *Options) error {
savedFile := ""
if runtime.GOOS == "windows" {
savedFile = targetFile
- if strings.HasSuffix(savedFile, ".exe") {
- savedFile = savedFile[:len(savedFile)-4]
- }
+ savedFile = strings.TrimSuffix(savedFile, ".exe")
savedFile += ".old.exe"
}
@@ -319,9 +317,7 @@ func makeRandomExeName(baseName, extension string) (string, error) {
const maxAttempts = 5
if runtime.GOOS == "windows" {
- if strings.HasSuffix(baseName, ".exe") {
- baseName = baseName[:len(baseName)-4]
- }
+ baseName = strings.TrimSuffix(baseName, ".exe")
extension += ".exe"
}
diff --git a/cmd/serve/docker/docker.go b/cmd/serve/docker/docker.go
index f9f6fcd939be4..b9702d87f3203 100644
--- a/cmd/serve/docker/docker.go
+++ b/cmd/serve/docker/docker.go
@@ -20,7 +20,7 @@ var (
pluginName = "rclone"
pluginScope = "local"
baseDir = "/var/lib/docker-volumes/rclone"
- sockDir = "/run/docker/plugins"
+ sockDir = "/run/docker/plugins" // location of unix sockets (only relevant on Linux and FreeBSD)
defSpecDir = "/etc/docker/plugins"
stateFile = "docker-plugin.state"
socketAddr = "" // TCP listening address or empty string for Unix socket
diff --git a/cmd/serve/proxy/proxy.go b/cmd/serve/proxy/proxy.go
index 861861ee99d85..9e1d15878ba2f 100644
--- a/cmd/serve/proxy/proxy.go
+++ b/cmd/serve/proxy/proxy.go
@@ -156,11 +156,11 @@ func (p *Proxy) run(in map[string]string) (config configmap.Simple, err error) {
fs.Debugf(nil, "Calling proxy %v", p.cmdLine)
duration := time.Since(start)
if err != nil {
- return nil, fmt.Errorf("proxy: failed on %v: %q: %w", p.cmdLine, strings.TrimSpace(string(stderr.Bytes())), err)
+ return nil, fmt.Errorf("proxy: failed on %v: %q: %w", p.cmdLine, strings.TrimSpace(stderr.String()), err)
}
err = json.Unmarshal(stdout.Bytes(), &config)
if err != nil {
- return nil, fmt.Errorf("proxy: failed to read output: %q: %w", string(stdout.Bytes()), err)
+ return nil, fmt.Errorf("proxy: failed to read output: %q: %w", stdout.String(), err)
}
fs.Debugf(nil, "Proxy returned in %v", duration)
diff --git a/cmd/test/makefiles/makefiles.go b/cmd/test/makefiles/makefiles.go
index 83a1f5842099f..b778accd0785d 100644
--- a/cmd/test/makefiles/makefiles.go
+++ b/cmd/test/makefiles/makefiles.go
@@ -254,7 +254,6 @@ func (d *dir) createDirectories() {
return
}
}
- return
}
// list the directory hierarchy
diff --git a/fs/accounting/accounting.go b/fs/accounting/accounting.go
index e3ed4972c83da..53d6549fb65d0 100644
--- a/fs/accounting/accounting.go
+++ b/fs/accounting/accounting.go
@@ -444,7 +444,7 @@ func (acc *Account) speed() (bps, current float64) {
return 0, 0
}
// Calculate speed from first read.
- total := float64(time.Now().Sub(acc.values.start)) / float64(time.Second)
+ total := float64(time.Since(acc.values.start)) / float64(time.Second)
if total > 0 {
bps = float64(acc.values.bytes) / total
} else {
diff --git a/fs/accounting/stats_groups_test.go b/fs/accounting/stats_groups_test.go
index 2fd08c39df9a6..c111a08d80dc3 100644
--- a/fs/accounting/stats_groups_test.go
+++ b/fs/accounting/stats_groups_test.go
@@ -111,9 +111,9 @@ func TestStatsGroupOperations(t *testing.T) {
runtime.GC()
runtime.ReadMemStats(&end)
- t.Log(fmt.Sprintf("%+v\n%+v", start, end))
+ t.Logf("%+v\n%+v", start, end)
diff := percentDiff(start.HeapObjects, end.HeapObjects)
- if diff > 1 || diff < 0 {
+ if diff > 1 {
t.Errorf("HeapObjects = %d, expected %d", end.HeapObjects, start.HeapObjects)
}
})
diff --git a/fs/asyncreader/asyncreader.go b/fs/asyncreader/asyncreader.go
index 44f8fb24528c3..a39ae880724b4 100644
--- a/fs/asyncreader/asyncreader.go
+++ b/fs/asyncreader/asyncreader.go
@@ -68,8 +68,8 @@ func (a *AsyncReader) init(rd io.ReadCloser, buffers int) {
a.in = rd
a.ready = make(chan *buffer, buffers)
a.token = make(chan struct{}, buffers)
- a.exit = make(chan struct{}, 0)
- a.exited = make(chan struct{}, 0)
+ a.exit = make(chan struct{})
+ a.exited = make(chan struct{})
a.buffers = buffers
a.cur = nil
a.size = softStartInitial
diff --git a/fs/config/configfile/configfile.go b/fs/config/configfile/configfile.go
index 65971492d6cff..df01f25a5499e 100644
--- a/fs/config/configfile/configfile.go
+++ b/fs/config/configfile/configfile.go
@@ -187,10 +187,7 @@ func (s *Storage) Serialize() (string, error) {
func (s *Storage) HasSection(section string) bool {
s.check()
_, err := s.gc.GetSection(section)
- if err != nil {
- return false
- }
- return true
+ return err == nil
}
// DeleteSection removes the named section and all config from the
diff --git a/fs/config/rc.go b/fs/config/rc.go
index fbbc7bdc9b2d3..db5d48ccaea32 100644
--- a/fs/config/rc.go
+++ b/fs/config/rc.go
@@ -72,10 +72,7 @@ See the [listremotes command](/commands/rclone_listremotes/) command for more in
// Return the a list of remotes in the config file
func rcListRemotes(ctx context.Context, in rc.Params) (out rc.Params, err error) {
- var remotes = []string{}
- for _, remote := range LoadedData().GetSectionList() {
- remotes = append(remotes, remote)
- }
+ remotes := LoadedData().GetSectionList()
out = rc.Params{
"remotes": remotes,
}
diff --git a/fs/filter/filter.go b/fs/filter/filter.go
index 37071f9d26646..e35018fa50112 100644
--- a/fs/filter/filter.go
+++ b/fs/filter/filter.go
@@ -596,7 +596,7 @@ func (f *Filter) UsesDirectoryFilters() bool {
}
rule := f.dirRules.rules[0]
re := rule.Regexp.String()
- if rule.Include == true && re == "^.*$" {
+ if rule.Include && re == "^.*$" {
return false
}
return true
diff --git a/fs/fserrors/error.go b/fs/fserrors/error.go
index 4cdf7ac9d1ad9..12c971cd2c5ec 100644
--- a/fs/fserrors/error.go
+++ b/fs/fserrors/error.go
@@ -258,7 +258,7 @@ func NewErrorRetryAfter(d time.Duration) ErrorRetryAfter {
// Error returns the textual version of the error
func (e ErrorRetryAfter) Error() string {
- return fmt.Sprintf("try again after %v (%v)", time.Time(e).Format(time.RFC3339Nano), time.Time(e).Sub(time.Now()))
+ return fmt.Sprintf("try again after %v (%v)", time.Time(e).Format(time.RFC3339Nano), time.Until(time.Time(e)))
}
// RetryAfter returns the time the operation should be retried at or
diff --git a/fs/fserrors/error_test.go b/fs/fserrors/error_test.go
index a0cf4e2e668f7..ec8426660af37 100644
--- a/fs/fserrors/error_test.go
+++ b/fs/fserrors/error_test.go
@@ -181,7 +181,7 @@ func TestShouldRetry(t *testing.T) {
func TestRetryAfter(t *testing.T) {
e := NewErrorRetryAfter(time.Second)
after := e.RetryAfter()
- dt := after.Sub(time.Now())
+ dt := time.Until(after)
assert.True(t, dt >= 900*time.Millisecond && dt <= 1100*time.Millisecond)
assert.True(t, IsRetryAfterError(e))
assert.False(t, IsRetryAfterError(io.EOF))
diff --git a/fs/fspath/path.go b/fs/fspath/path.go
index 99bdbe28b6315..285d843fff760 100644
--- a/fs/fspath/path.go
+++ b/fs/fspath/path.go
@@ -99,7 +99,7 @@ func Parse(path string) (parsed Parsed, err error) {
return parsed, errCantBeEmpty
}
// If path has no `:` in, it must be a local path
- if strings.IndexRune(path, ':') < 0 {
+ if !strings.ContainsRune(path, ':') {
return parsed, nil
}
// States for parser
diff --git a/fs/log/caller_hook.go b/fs/log/caller_hook.go
index 7a72999e4fedf..13941bc02308e 100644
--- a/fs/log/caller_hook.go
+++ b/fs/log/caller_hook.go
@@ -45,7 +45,7 @@ func findCaller(skip int) string {
line := 0
for i := 0; i < 10; i++ {
file, line = getCaller(skip + i)
- if !strings.HasPrefix(file, "logrus") && strings.Index(file, "log.go") < 0 {
+ if !strings.HasPrefix(file, "logrus") && !strings.Contains(file, "log.go") {
break
}
}
diff --git a/fs/open_options.go b/fs/open_options.go
index 84e43045c20d2..938e38d04fd6f 100644
--- a/fs/open_options.go
+++ b/fs/open_options.go
@@ -75,7 +75,7 @@ func ParseRangeOption(s string) (po *RangeOption, err error) {
return nil, errors.New("Range: header invalid: doesn't start with " + preamble)
}
s = s[len(preamble):]
- if strings.IndexRune(s, ',') >= 0 {
+ if strings.ContainsRune(s, ',') {
return nil, errors.New("Range: header invalid: contains multiple ranges which isn't supported")
}
dash := strings.IndexRune(s, '-')
@@ -250,7 +250,7 @@ func (o NullOption) Header() (key string, value string) {
// String formats the option into human-readable form
func (o NullOption) String() string {
- return fmt.Sprintf("NullOption()")
+ return "NullOption()"
}
// Mandatory returns whether the option must be parsed or can be ignored
diff --git a/fs/rc/rcserver/rcserver.go b/fs/rc/rcserver/rcserver.go
index e2f8bd22ee80f..0d246a2f60cdf 100644
--- a/fs/rc/rcserver/rcserver.go
+++ b/fs/rc/rcserver/rcserver.go
@@ -378,7 +378,7 @@ func (s *Server) handleGet(w http.ResponseWriter, r *http.Request, path string)
if s.opt.WebUI {
pluginsMatchResult := webgui.PluginsMatch.FindStringSubmatch(path)
- if pluginsMatchResult != nil && len(pluginsMatchResult) > 2 {
+ if len(pluginsMatchResult) > 2 {
ok := webgui.ServePluginOK(w, r, pluginsMatchResult)
if !ok {
r.URL.Path = fmt.Sprintf("/%s/%s/app/build/%s", pluginsMatchResult[1], pluginsMatchResult[2], pluginsMatchResult[3])
diff --git a/fs/rc/webgui/plugins.go b/fs/rc/webgui/plugins.go
index 3a97fd6795a76..8f212be582e5e 100644
--- a/fs/rc/webgui/plugins.go
+++ b/fs/rc/webgui/plugins.go
@@ -293,7 +293,7 @@ func ServePluginOK(w http.ResponseWriter, r *http.Request, pluginsMatchResult []
return true
}
-var referrerPathReg = regexp.MustCompile("^(https?):\\/\\/(.+):([0-9]+)?\\/(.*)\\/?\\?(.*)$")
+var referrerPathReg = regexp.MustCompile(`^(https?):\/\/(.+):([0-9]+)?\/(.*)\/?\?(.*)$`)
// ServePluginWithReferrerOK check if redirectReferrer is set for the referred a plugin, if yes,
// sends a redirect to actual url. This function is useful for plugins to refer to absolute paths when
@@ -306,9 +306,9 @@ func ServePluginWithReferrerOK(w http.ResponseWriter, r *http.Request, path stri
referrer := r.Referer()
referrerPathMatch := referrerPathReg.FindStringSubmatch(referrer)
- if referrerPathMatch != nil && len(referrerPathMatch) > 3 {
+ if len(referrerPathMatch) > 3 {
referrerPluginMatch := PluginsMatch.FindStringSubmatch(referrerPathMatch[4])
- if referrerPluginMatch != nil && len(referrerPluginMatch) > 2 {
+ if len(referrerPluginMatch) > 2 {
pluginKey := fmt.Sprintf("%s/%s", referrerPluginMatch[1], referrerPluginMatch[2])
currentPlugin, err := loadedPlugins.GetPluginByName(pluginKey)
if err != nil {
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 11cf4dc8bb994..58481416a56b9 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -387,8 +387,7 @@ func Run(t *testing.T, opt *Opt) {
// Skip if remote is not SetTier and GetTier capable
skipIfNotSetTier := func(t *testing.T) {
skipIfNotOk(t)
- if f.Features().SetTier == false ||
- f.Features().GetTier == false {
+ if !f.Features().SetTier || !f.Features().GetTier {
t.Skip("FS has no SetTier & GetTier interfaces")
}
}
@@ -735,7 +734,7 @@ func Run(t *testing.T, opt *Opt) {
TestPutLarge(ctx, t, f, &fstest.Item{
ModTime: fstest.Time("2001-02-03T04:05:06.499999999Z"),
- Path: fmt.Sprintf("zero-length-file"),
+ Path: "zero-length-file",
Size: int64(0),
})
})
diff --git a/lib/encoder/internal/gen/main.go b/lib/encoder/internal/gen/main.go
index 38d33e1caf494..a94c3793580df 100644
--- a/lib/encoder/internal/gen/main.go
+++ b/lib/encoder/internal/gen/main.go
@@ -465,17 +465,13 @@ func getMapping(mask encoder.MultiEncoder) mapping {
}
func collectEncodables(m []mapping) (out []rune) {
for _, s := range m {
- for _, r := range s.src {
- out = append(out, r)
- }
+ out = append(out, s.src...)
}
return
}
func collectEncoded(m []mapping) (out []rune) {
for _, s := range m {
- for _, r := range s.dst {
- out = append(out, r)
- }
+ out = append(out, s.dst...)
}
return
}
diff --git a/lib/kv/internal_test.go b/lib/kv/internal_test.go
index ae40c6ed4a11a..1c0834fbba5c9 100644
--- a/lib/kv/internal_test.go
+++ b/lib/kv/internal_test.go
@@ -17,7 +17,6 @@ func TestKvConcurrency(t *testing.T) {
require.Equal(t, 0, len(dbMap), "no databases can be started initially")
const threadNum = 5
- const facility = "test"
var wg sync.WaitGroup
ctx := context.Background()
results := make([]*DB, threadNum)
@@ -55,7 +54,6 @@ func TestKvConcurrency(t *testing.T) {
func TestKvExit(t *testing.T) {
require.Equal(t, 0, len(dbMap), "no databases can be started initially")
const dbNum = 5
- const openNum = 2
ctx := context.Background()
for i := 0; i < dbNum; i++ {
facility := fmt.Sprintf("test-%d", i)
diff --git a/lib/oauthutil/oauthutil.go b/lib/oauthutil/oauthutil.go
index 56c936d712c9d..53a08900c0bcc 100644
--- a/lib/oauthutil/oauthutil.go
+++ b/lib/oauthutil/oauthutil.go
@@ -280,7 +280,7 @@ func (ts *TokenSource) timeToExpiry() time.Duration {
if t.Expiry.IsZero() {
return 3e9 * time.Second // ~95 years
}
- return t.Expiry.Sub(time.Now())
+ return time.Until(t.Expiry)
}
// OnExpiry returns a channel which has the time written to it when
@@ -766,7 +766,6 @@ func (s *authServer) Init() error {
}
fs.Debugf(nil, "Redirecting browser to: %s", s.authURL)
http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect)
- return
})
mux.HandleFunc("/", s.handleAuth)
diff --git a/lib/pacer/tokens.go b/lib/pacer/tokens.go
index b4f905ba9be85..d1e1e7cea7559 100644
--- a/lib/pacer/tokens.go
+++ b/lib/pacer/tokens.go
@@ -22,7 +22,6 @@ func NewTokenDispenser(n int) *TokenDispenser {
// Get gets a token from the pool - don't forget to return it with Put
func (td *TokenDispenser) Get() {
<-td.tokens
- return
}
// Put returns a token
diff --git a/lib/version/version.go b/lib/version/version.go
index 0aebe3d61dbfa..e8a4c17f6bdbd 100644
--- a/lib/version/version.go
+++ b/lib/version/version.go
@@ -11,7 +11,7 @@ import (
const versionFormat = "-v2006-01-02-150405.000"
-var versionRegexp = regexp.MustCompile("-v\\d{4}-\\d{2}-\\d{2}-\\d{6}-\\d{3}")
+var versionRegexp = regexp.MustCompile(`-v\d{4}-\d{2}-\d{2}-\d{6}-\d{3}`)
// Split fileName into base and extension so that base + ext == fileName
func splitExt(fileName string) (base, ext string) {
diff --git a/vfs/dir.go b/vfs/dir.go
index 2b2eda5a29e58..004595b77435b 100644
--- a/vfs/dir.go
+++ b/vfs/dir.go
@@ -506,7 +506,7 @@ func (d *Dir) _purgeVirtual() {
// if remote can have empty directories then a
// new dir will be read in the listing
d._deleteVirtual(name)
- } else {
+ //} else {
// leave the empty directory marker
}
case vAddFile:
diff --git a/vfs/read.go b/vfs/read.go
index a128b03133610..88bcabfce2109 100644
--- a/vfs/read.go
+++ b/vfs/read.go
@@ -477,7 +477,7 @@ func (fh *ReadFileHandle) Release() error {
err := fh.close()
if err != nil {
fs.Errorf(fh.remote, "ReadFileHandle.Release error: %v", err)
- } else {
+ //} else {
// fs.Debugf(fh.remote, "ReadFileHandle.Release OK")
}
return err
diff --git a/vfs/vfscache/downloaders/downloaders.go b/vfs/vfscache/downloaders/downloaders.go
index c6d769a4fbfc3..42ca677fd8931 100644
--- a/vfs/vfscache/downloaders/downloaders.go
+++ b/vfs/vfscache/downloaders/downloaders.go
@@ -359,7 +359,7 @@ func (dls *Downloaders) _ensureDownloader(r ranges.Range) (err error) {
return nil
}
// Downloader not found so start a new one
- dl, err = dls._newDownloader(r)
+ _, err = dls._newDownloader(r)
if err != nil {
dls._countErrors(0, err)
return fmt.Errorf("failed to start downloader: %w", err)
diff --git a/vfs/vfscache/item.go b/vfs/vfscache/item.go
index d61d6062adf25..d6a6e20b8d215 100644
--- a/vfs/vfscache/item.go
+++ b/vfs/vfscache/item.go
@@ -779,7 +779,7 @@ func (item *Item) _checkObject(o fs.Object) error {
} else {
fs.Debugf(item.name, "vfs cache: remote object has gone but local object modified - keeping it")
}
- } else {
+ //} else {
// no remote object && no local object
// OK
}
@@ -946,7 +946,7 @@ func (item *Item) Reset() (rr ResetResult, spaceFreed int64, err error) {
/* Do not need to reset an empty cache file unless it was being reset and the reset failed.
Some thread(s) may be waiting on the reset's succesful completion in that case. */
- if item.info.Rs.Size() == 0 && item.beingReset == false {
+ if item.info.Rs.Size() == 0 && !item.beingReset {
return SkippedEmpty, 0, nil
}
diff --git a/vfs/vfstest/write_non_unix.go b/vfs/vfstest/write_non_unix.go
index 84b5bf00a4421..637d5b5bd43a6 100644
--- a/vfs/vfstest/write_non_unix.go
+++ b/vfs/vfstest/write_non_unix.go
@@ -17,10 +17,7 @@ func TestWriteFileDoubleClose(t *testing.T) {
// writeTestDup performs the platform-specific implementation of the dup() syscall
func writeTestDup(oldfd uintptr) (uintptr, error) {
- p, err := windows.GetCurrentProcess()
- if err != nil {
- return 0, err
- }
+ p := windows.CurrentProcess()
var h windows.Handle
return uintptr(h), windows.DuplicateHandle(p, windows.Handle(oldfd), p, &h, 0, true, windows.DUPLICATE_SAME_ACCESS)
}
diff --git a/vfs/write.go b/vfs/write.go
index 4f05903123d53..24595e0419ccb 100644
--- a/vfs/write.go
+++ b/vfs/write.go
@@ -247,7 +247,7 @@ func (fh *WriteFileHandle) Flush() error {
err := fh.close()
if err != nil {
fs.Errorf(fh.remote, "WriteFileHandle.Flush error: %v", err)
- } else {
+ //} else {
// fs.Debugf(fh.remote, "WriteFileHandle.Flush OK")
}
return err
@@ -268,7 +268,7 @@ func (fh *WriteFileHandle) Release() error {
err := fh.close()
if err != nil {
fs.Errorf(fh.remote, "WriteFileHandle.Release error: %v", err)
- } else {
+ //} else {
// fs.Debugf(fh.remote, "WriteFileHandle.Release OK")
}
return err
From 02b4638a2212b903c1730d82acabc49816acee66 Mon Sep 17 00:00:00 2001
From: m00594701
Date: Tue, 7 Jun 2022 15:41:46 +0800
Subject: [PATCH 036/560] backend: add Huawei OBS to s3 provider list
---
README.md | 1 +
backend/s3/s3.go | 116 ++++++++++++++++++++++++++-
docs/content/s3.md | 192 ++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 295 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index 206517733e77a..7a955d31f5162 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@ Rclone *("rsync for cloud storage")* is a command-line program to sync files and
* Google Photos [:page_facing_up:](https://rclone.org/googlephotos/)
* HDFS (Hadoop Distributed Filesystem) [:page_facing_up:](https://rclone.org/hdfs/)
* HTTP [:page_facing_up:](https://rclone.org/http/)
+ * Huawei Cloud Object Storage Service(OBS) [:page_facing_up:](https://rclone.org/s3/#huawei-obs)
* Hubic [:page_facing_up:](https://rclone.org/hubic/)
* Internet Archive [:page_facing_up:](https://rclone.org/internetarchive/)
* Jottacloud [:page_facing_up:](https://rclone.org/jottacloud/)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index 0a7b216f488ea..cd4f3e06b9ca3 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -60,7 +60,7 @@ import (
func init() {
fs.Register(&fs.RegInfo{
Name: "s3",
- Description: "Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi",
+ Description: "Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi",
NewFs: NewFs,
CommandHelp: commandHelp,
Options: []fs.Option{{
@@ -92,6 +92,9 @@ func init() {
}, {
Value: "Dreamhost",
Help: "Dreamhost DreamObjects",
+ }, {
+ Value: "HuaweiOBS",
+ Help: "Huawei Object Storage Service",
}, {
Value: "IBMCOS",
Help: "IBM COS S3",
@@ -305,6 +308,56 @@ func init() {
Value: "pl-waw",
Help: "Warsaw, Poland",
}},
+ }, {
+ Name: "region",
+ Help: "Region to connect to. - the location where your bucket will be created and your data stored. Need bo be same with your endpoint.\n",
+ Provider: "HuaweiOBS",
+ Examples: []fs.OptionExample{{
+ Value: "af-south-1",
+ Help: "AF-Johannesburg",
+ }, {
+ Value: "ap-southeast-2",
+ Help: "AP-Bangkok",
+ }, {
+ Value: "ap-southeast-3",
+ Help: "AP-Singapore",
+ }, {
+ Value: "cn-east-3",
+ Help: "CN East-Shanghai1",
+ }, {
+ Value: "cn-east-2",
+ Help: "CN East-Shanghai2",
+ }, {
+ Value: "cn-north-1",
+ Help: "CN North-Beijing1",
+ }, {
+ Value: "cn-north-4",
+ Help: "CN North-Beijing4",
+ }, {
+ Value: "cn-south-1",
+ Help: "CN South-Guangzhou",
+ }, {
+ Value: "ap-southeast-1",
+ Help: "CN-Hong Kong",
+ }, {
+ Value: "sa-argentina-1",
+ Help: "LA-Buenos Aires1",
+ }, {
+ Value: "sa-peru-1",
+ Help: "LA-Lima1",
+ }, {
+ Value: "na-mexico-1",
+ Help: "LA-Mexico City1",
+ }, {
+ Value: "sa-chile-1",
+ Help: "LA-Santiago2",
+ }, {
+ Value: "sa-brazil-1",
+ Help: "LA-Sao Paulo1",
+ }, {
+ Value: "ru-northwest-2",
+ Help: "RU-Moscow2",
+ }},
}, {
Name: "region",
Help: "Region to connect to.",
@@ -316,7 +369,7 @@ func init() {
}, {
Name: "region",
Help: "Region to connect to.\n\nLeave blank if you are using an S3 clone and you don't have a region.",
- Provider: "!AWS,Alibaba,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS",
+ Provider: "!AWS,Alibaba,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS,HuaweiOBS",
Examples: []fs.OptionExample{{
Value: "",
Help: "Use this if unsure.\nWill use v4 signatures and an empty region.",
@@ -708,6 +761,57 @@ func init() {
Value: "oss-me-east-1.aliyuncs.com",
Help: "Middle East 1 (Dubai)",
}},
+ }, {
+ // obs endpoints: https://developer.huaweicloud.com/intl/en-us/endpoint?OBS
+ Name: "endpoint",
+ Help: "Endpoint for OBS API.",
+ Provider: "HuaweiOBS",
+ Examples: []fs.OptionExample{{
+ Value: "obs.af-south-1.myhuaweicloud.com",
+ Help: "AF-Johannesburg",
+ }, {
+ Value: "obs.ap-southeast-2.myhuaweicloud.com",
+ Help: "AP-Bangkok",
+ }, {
+ Value: "obs.ap-southeast-3.myhuaweicloud.com",
+ Help: "AP-Singapore",
+ }, {
+ Value: "obs.cn-east-3.myhuaweicloud.com",
+ Help: "CN East-Shanghai1",
+ }, {
+ Value: "obs.cn-east-2.myhuaweicloud.com",
+ Help: "CN East-Shanghai2",
+ }, {
+ Value: "obs.cn-north-1.myhuaweicloud.com",
+ Help: "CN North-Beijing1",
+ }, {
+ Value: "obs.cn-north-4.myhuaweicloud.com",
+ Help: "CN North-Beijing4",
+ }, {
+ Value: "obs.cn-south-1.myhuaweicloud.com",
+ Help: "CN South-Guangzhou",
+ }, {
+ Value: "obs.ap-southeast-1.myhuaweicloud.com",
+ Help: "CN-Hong Kong",
+ }, {
+ Value: "obs.sa-argentina-1.myhuaweicloud.com",
+ Help: "LA-Buenos Aires1",
+ }, {
+ Value: "obs.sa-peru-1.myhuaweicloud.com",
+ Help: "LA-Lima1",
+ }, {
+ Value: "obs.na-mexico-1.myhuaweicloud.com",
+ Help: "LA-Mexico City1",
+ }, {
+ Value: "obs.sa-chile-1.myhuaweicloud.com",
+ Help: "LA-Santiago2",
+ }, {
+ Value: "obs.sa-brazil-1.myhuaweicloud.com",
+ Help: "LA-Sao Paulo1",
+ }, {
+ Value: "obs.ru-northwest-2.myhuaweicloud.com",
+ Help: "RU-Moscow2",
+ }},
}, {
Name: "endpoint",
Help: "Endpoint for Scaleway Object Storage.",
@@ -879,7 +983,7 @@ func init() {
}, {
Name: "endpoint",
Help: "Endpoint for S3 API.\n\nRequired when using an S3 clone.",
- Provider: "!AWS,IBMCOS,TencentCOS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp",
+ Provider: "!AWS,IBMCOS,TencentCOS,HuaweiOBS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp",
Examples: []fs.OptionExample{{
Value: "objects-us-east-1.dream.io",
Help: "Dream Objects endpoint",
@@ -1289,7 +1393,7 @@ func init() {
}, {
Name: "location_constraint",
Help: "Location constraint - must be set to match the Region.\n\nLeave blank if not sure. Used when creating buckets only.",
- Provider: "!AWS,IBMCOS,Alibaba,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS",
+ Provider: "!AWS,IBMCOS,Alibaba,HuaweiOBS,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS",
}, {
Name: "acl",
Help: `Canned ACL used when creating buckets and storing or copying objects.
@@ -2248,6 +2352,10 @@ func setQuirks(opt *Options) {
// No quirks
case "Alibaba":
useMultipartEtag = false // Alibaba seems to calculate multipart Etags differently from AWS
+ case "HuaweiOBS":
+ // Huawei OBS PFS is not support listObjectV2, and if turn on the urlEncodeListing, marker will not work and keep list same page forever.
+ urlEncodeListings = false
+ listObjectsV2 = false
case "Ceph":
listObjectsV2 = false
virtualHostStyle = false
diff --git a/docs/content/s3.md b/docs/content/s3.md
index 8c01c8c13b2ee..15b26e7e2944c 100644
--- a/docs/content/s3.md
+++ b/docs/content/s3.md
@@ -16,6 +16,7 @@ The S3 backend can be used with a number of different providers:
{{< provider name="Arvan Cloud Object Storage (AOS)" home="https://www.arvancloud.com/en/products/cloud-storage" config="/s3/#arvan-cloud-object-storage-aos" >}}
{{< provider name="DigitalOcean Spaces" home="https://www.digitalocean.com/products/object-storage/" config="/s3/#digitalocean-spaces" >}}
{{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}}
+{{< provider name="Huawei OBS" home="https://www.huaweicloud.com/intl/en-us/product/obs.html" config="/s3/#huawei-obs" >}}
{{< provider name="IBM COS S3" home="http://www.ibm.com/cloud/object-storage" config="/s3/#ibm-cos-s3" >}}
{{< provider name="Minio" home="https://www.minio.io/" config="/s3/#minio" >}}
{{< provider name="RackCorp Object Storage" home="https://www.rackcorp.com/" config="/s3/#RackCorp" >}}
@@ -569,7 +570,7 @@ A simple solution is to set the `--s3-upload-cutoff 0` and force all the files t
{{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/s3/s3.go then run make backenddocs" >}}
### Standard options
-Here are the standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
+Here are the standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
#### --s3-provider
@@ -596,6 +597,8 @@ Properties:
- Digital Ocean Spaces
- "Dreamhost"
- Dreamhost DreamObjects
+ - "HuaweiOBS"
+ - Huawei Object Storage Service
- "IBMCOS"
- IBM COS S3
- "LyveCloud"
@@ -833,7 +836,7 @@ Properties:
- Config: region
- Env Var: RCLONE_S3_REGION
-- Provider: !AWS,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS
+- Provider: !AWS,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS,HuaweiOBS
- Type: string
- Required: false
- Examples:
@@ -946,6 +949,48 @@ Endpoint for Arvan Cloud Object Storage (AOS) API.
- "s3.ir-tbz-sh1.arvanstorage.com"
- Tabriz Iran (Shahriar)
+#### --s3-endpoint
+
+Endpoint for Huawei Cloud Object Storage Service (OBS) API.
+
+- Config: endpoint
+- Env Var: RCLONE_S3_ENDPOINT
+- Provider: HuaweiOBS
+- Type: string
+- Required: false
+- Examples:
+ - "obs.af-south-1.myhuaweicloud.com"
+ - AF-Johannesburg Endpoint
+ - "obs.ap-southeast-2.myhuaweicloud.com"
+ - AP-Bangkok Endpoint
+ - "obs.ap-southeast-3.myhuaweicloud.com"
+ - AP-Singapore Endpoint
+ - "obs.cn-east-3.myhuaweicloud.com"
+ - CN East-Shanghai1 Endpoint
+ - "obs.cn-east-2.myhuaweicloud.com"
+ - CN East-Shanghai2 Endpoint
+ - "obs.cn-north-1.myhuaweicloud.com"
+ - CN North-Beijing1 Endpoint
+ - "obs.cn-north-4.myhuaweicloud.com"
+ - CN North-Beijing4 Endpoint
+ - "obs.cn-south-1.myhuaweicloud.com"
+ - CN South-Guangzhou Endpoint
+ - "obs.ap-southeast-1.myhuaweicloud.com"
+ - CN-Hong Kong Endpoint
+ - "obs.sa-argentina-1.myhuaweicloud.com"
+ - LA-Buenos Aires1 Endpoint
+ - "obs.sa-peru-1.myhuaweicloud.com"
+ - LA-Lima1 Endpoint
+ - "obs.na-mexico-1.myhuaweicloud.com"
+ - LA-Mexico City1 Endpoint
+ - "obs.sa-chile-1.myhuaweicloud.com"
+ - LA-Santiago2 Endpoint
+ - "obs.sa-brazil-1.myhuaweicloud.com"
+ - LA-Sao Paulo1 Endpoint
+ - "obs.ru-northwest-2.myhuaweicloud.com"
+ - RU-Moscow2 Endpoint
+
+
#### --s3-endpoint
@@ -1317,7 +1362,7 @@ Properties:
- Config: endpoint
- Env Var: RCLONE_S3_ENDPOINT
-- Provider: !AWS,IBMCOS,TencentCOS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp
+- Provider: !AWS,IBMCOS,TencentCOS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp,HuaweiOBS
- Type: string
- Required: false
- Examples:
@@ -1556,7 +1601,7 @@ Properties:
- Config: location_constraint
- Env Var: RCLONE_S3_LOCATION_CONSTRAINT
-- Provider: !AWS,IBMCOS,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS
+- Provider: !AWS,IBMCOS,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS,HuaweiOBS
- Type: string
- Required: false
@@ -1784,7 +1829,7 @@ Properties:
### Advanced options
-Here are the advanced options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
+Here are the advanced options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
#### --s3-bucket-acl
@@ -2590,7 +2635,7 @@ Option Storage.
Type of storage to configure.
Choose a number from below, or type in your own value.
...
-XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi
+XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi
\ (s3)
...
Storage> s3
@@ -2731,6 +2776,133 @@ Once configured, you can create a new Space and begin copying files. For example
rclone mkdir spaces:my-new-space
rclone copy /path/to/files spaces:my-new-space
```
+### Huawei OBS {#huawei-obs}
+
+Object Storage Service (OBS) provides stable, secure, efficient, and easy-to-use cloud storage that lets you store virtually any volume of unstructured data in any format and access it from anywhere.
+
+OBS provides an S3 interface, you can copy and modify the following configuration and add it to your rclone configuration file.
+```
+[obs]
+type = s3
+provider = HuaweiOBS
+access_key_id = your-access-key-id
+secret_access_key = your-secret-access-key
+region = af-south-1
+endpoint = obs.af-south-1.myhuaweicloud.com
+acl = private
+```
+
+Or you can also configure via the interactive command line:
+```
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+name> obs
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+[snip]
+ 5 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi
+ \ (s3)
+[snip]
+Storage> 5
+Option provider.
+Choose your S3 provider.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+[snip]
+ 9 / Huawei Object Storage Service
+ \ (HuaweiOBS)
+[snip]
+provider> 9
+Option env_auth.
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
+Only applies if access_key_id and secret_access_key is blank.
+Choose a number from below, or type in your own boolean value (true or false).
+Press Enter for the default (false).
+ 1 / Enter AWS credentials in the next step.
+ \ (false)
+ 2 / Get AWS credentials from the environment (env vars or IAM).
+ \ (true)
+env_auth> 1
+Option access_key_id.
+AWS Access Key ID.
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+access_key_id> your-access-key-id
+Option secret_access_key.
+AWS Secret Access Key (password).
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+secret_access_key> your-secret-access-key
+Option region.
+Region to connect to.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / AF-Johannesburg
+ \ (af-south-1)
+ 2 / AP-Bangkok
+ \ (ap-southeast-2)
+[snip]
+region> 1
+Option endpoint.
+Endpoint for OBS API.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / AF-Johannesburg
+ \ (obs.af-south-1.myhuaweicloud.com)
+ 2 / AP-Bangkok
+ \ (obs.ap-southeast-2.myhuaweicloud.com)
+[snip]
+endpoint> 1
+Option acl.
+Canned ACL used when creating buckets and storing or copying objects.
+This ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.
+For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+Note that this ACL is applied when server-side copying objects as S3
+doesn't copy the ACL from the source but rather writes a fresh one.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ / Owner gets FULL_CONTROL.
+ 1 | No one else has access rights (default).
+ \ (private)
+[snip]
+acl> 1
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n>
+--------------------
+[obs]
+type = s3
+provider = HuaweiOBS
+access_key_id = your-access-key-id
+secret_access_key = your-secret-access-key
+region = af-south-1
+endpoint = obs.af-south-1.myhuaweicloud.com
+acl = private
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+Current remotes:
+
+Name Type
+==== ====
+obs s3
+
+e) Edit existing remote
+n) New remote
+d) Delete remote
+r) Rename remote
+c) Copy remote
+s) Set configuration password
+q) Quit config
+e/n/d/r/c/s/q> q
+```
### IBM COS (S3)
@@ -3045,7 +3217,7 @@ Choose `s3` backend
Type of storage to configure.
Choose a number from below, or type in your own value.
[snip]
-XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS
+XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS
\ (s3)
[snip]
Storage> s3
@@ -3346,7 +3518,7 @@ Type of storage to configure.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
[snip]
- 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, IBM COS, Minio, and Tencent COS
+ 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Minio, and Tencent COS
\ "s3"
[snip]
Storage> s3
@@ -3456,7 +3628,7 @@ Option Storage.
Type of storage to configure.
Choose a number from below, or type in your own value.
...
- 5 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS
+ 5 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS
\ (s3)
...
Storage> s3
@@ -3827,7 +3999,7 @@ Choose a number from below, or type in your own value
\ "alias"
3 / Amazon Drive
\ "amazon cloud drive"
- 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, IBM COS, Minio, and Tencent COS
+ 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Minio, and Tencent COS
\ "s3"
[snip]
Storage> s3
From 6602e1a851c172248f379218c1ae762829c013eb Mon Sep 17 00:00:00 2001
From: "Art M. Gallagher"
Date: Tue, 14 Jun 2022 09:55:40 +0100
Subject: [PATCH 037/560] mega: document using MEGAcmd to help with login
failures
Added extra subsection under Failure to log-in
See: https://forum.rclone.org/t/30935
---
docs/content/mega.md | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/docs/content/mega.md b/docs/content/mega.md
index f3abf95fc9d7b..27882e34232fd 100644
--- a/docs/content/mega.md
+++ b/docs/content/mega.md
@@ -107,6 +107,44 @@ Use `rclone dedupe` to fix duplicated files.
### Failure to log-in
+#### Object not found
+
+If you are connecting to your Mega remote for the first time,
+to test access and syncronisation, you may receive an error such as
+
+```
+Failed to create file system for "my-mega-remote:":
+couldn't login: Object (typically, node or user) not found
+```
+
+The diagnostic steps often recommended in the [rclone forum](https://forum.rclone.org/search?q=mega)
+start with the **MEGAcmd** utility. Note that this refers to
+the official C++ command from https://github.com/meganz/MEGAcmd
+and not the go language built command from t3rm1n4l/megacmd
+that is no longer maintained.
+
+Follow the instructions for installing MEGAcmd and try accessing
+your remote as they recommend. You can establish whether or not
+you can log in using MEGAcmd, and obtain diagnostic information
+to help you, and search or work with others in the forum.
+
+```
+MEGA CMD> login me@example.com
+Password:
+Fetching nodes ...
+Loading transfers from local cache
+Login complete as me@example.com
+me@example.com:/$
+```
+
+Note that some have found issues with passwords containing special
+characters. If you can not log on with rclone, but MEGAcmd logs on
+just fine, then consider changing your password temporarily to
+pure alphanumeric characters, in case that helps.
+
+
+#### Repeated commands blocks access
+
Mega remotes seem to get blocked (reject logins) under "heavy use".
We haven't worked out the exact blocking rules but it seems to be
related to fast paced, successive rclone commands.
From 50c2e37aac746466ed4159ada1d40316ea302481 Mon Sep 17 00:00:00 2001
From: Sven Gerber <49589423+svengerber@users.noreply.github.com>
Date: Tue, 14 Jun 2022 11:21:23 +0200
Subject: [PATCH 038/560] onedrive: add access scopes option
By default, rclone always requests read and write permissions. No matter what settings you configure in the AAD application. This option allows to explicitly request readonly permissions
Migrated read only option to access scope option and set disable_site_permission option to hidden.
---
backend/onedrive/onedrive.go | 45 ++++++++++++++++++++++++++++--------
docs/content/onedrive.md | 7 ++++--
2 files changed, 41 insertions(+), 11 deletions(-)
diff --git a/backend/onedrive/onedrive.go b/backend/onedrive/onedrive.go
index a077843ed3f18..f8ab22a27480d 100644
--- a/backend/onedrive/onedrive.go
+++ b/backend/onedrive/onedrive.go
@@ -65,12 +65,12 @@ var (
authPath = "/common/oauth2/v2.0/authorize"
tokenPath = "/common/oauth2/v2.0/token"
- scopesWithSitePermission = []string{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "offline_access", "Sites.Read.All"}
- scopesWithoutSitePermission = []string{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "offline_access"}
+ scopeAccess = fs.SpaceSepList{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "Sites.Read.All", "offline_access"}
+ scopeAccessWithoutSites = fs.SpaceSepList{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "offline_access"}
// Description of how to auth for this app for a business account
oauthConfig = &oauth2.Config{
- Scopes: scopesWithSitePermission,
+ Scopes: scopeAccess,
ClientID: rcloneClientID,
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
RedirectURL: oauthutil.RedirectLocalhostURL,
@@ -150,6 +150,27 @@ there through a path traversal.
`,
Advanced: true,
}, {
+ Name: "access_scopes",
+ Help: `Set scopes to be requested by rclone.
+
+Choose or manually enter a custom space separated list with all scopes, that rclone should request.
+`,
+ Default: scopeAccess,
+ Advanced: true,
+ Examples: []fs.OptionExample{
+ {
+ Value: "Files.Read Files.ReadWrite Files.Read.All Files.ReadWrite.All Sites.Read.All offline_access",
+ Help: "Read and write access to all resources",
+ },
+ {
+ Value: "Files.Read Files.Read.All Sites.Read.All offline_access",
+ Help: "Read only access to all resources",
+ },
+ {
+ Value: "Files.Read Files.ReadWrite Files.Read.All Files.ReadWrite.All offline_access",
+ Help: "Read and write access to all resources, without the ability to browse SharePoint sites. \nSame as if disable_site_permission was set to true",
+ },
+ }}, {
Name: "disable_site_permission",
Help: `Disable the request for Sites.Read.All permission.
@@ -160,6 +181,7 @@ application, and your organization disallows users to consent app permission
request on their own.`,
Default: false,
Advanced: true,
+ Hide: fs.OptionHideBoth,
}, {
Name: "expose_onenote_files",
Help: `Set to make OneNote files show up in directory listings.
@@ -401,11 +423,16 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
region, graphURL := getRegionURL(m)
if config.State == "" {
+ var accessScopes fs.SpaceSepList
+ accessScopesString, _ := m.Get("access_scopes")
+ err := accessScopes.Set(accessScopesString)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse access_scopes: %w", err)
+ }
+ oauthConfig.Scopes = []string(accessScopes)
disableSitePermission, _ := m.Get("disable_site_permission")
if disableSitePermission == "true" {
- oauthConfig.Scopes = scopesWithoutSitePermission
- } else {
- oauthConfig.Scopes = scopesWithSitePermission
+ oauthConfig.Scopes = scopeAccessWithoutSites
}
oauthConfig.Endpoint = oauth2.Endpoint{
AuthURL: authEndpoint[region] + authPath,
@@ -562,6 +589,7 @@ type Options struct {
DriveType string `config:"drive_type"`
RootFolderID string `config:"root_folder_id"`
DisableSitePermission bool `config:"disable_site_permission"`
+ AccessScopes fs.SpaceSepList `config:"access_scopes"`
ExposeOneNoteFiles bool `config:"expose_onenote_files"`
ServerSideAcrossConfigs bool `config:"server_side_across_configs"`
ListChunk int64 `config:"list_chunk"`
@@ -829,10 +857,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
}
rootURL := graphAPIEndpoint[opt.Region] + "/v1.0" + "/drives/" + opt.DriveID
+ oauthConfig.Scopes = opt.AccessScopes
if opt.DisableSitePermission {
- oauthConfig.Scopes = scopesWithoutSitePermission
- } else {
- oauthConfig.Scopes = scopesWithSitePermission
+ oauthConfig.Scopes = scopeAccessWithoutSites
}
oauthConfig.Endpoint = oauth2.Endpoint{
AuthURL: authEndpoint[opt.Region] + authPath,
diff --git a/docs/content/onedrive.md b/docs/content/onedrive.md
index 7bb4558755934..a847d2a3879aa 100644
--- a/docs/content/onedrive.md
+++ b/docs/content/onedrive.md
@@ -132,12 +132,15 @@ Client ID and Key by following the steps below:
2. Enter a name for your app, choose account type `Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)`, select `Web` in `Redirect URI`, then type (do not copy and paste) `http://localhost:53682/` and click Register. Copy and keep the `Application (client) ID` under the app name for later use.
3. Under `manage` select `Certificates & secrets`, click `New client secret`. Enter a description (can be anything) and set `Expires` to 24 months. Copy and keep that secret _Value_ for later use (you _won't_ be able to see this value afterwards).
4. Under `manage` select `API permissions`, click `Add a permission` and select `Microsoft Graph` then select `delegated permissions`.
-5. Search and select the following permissions: `Files.Read`, `Files.ReadWrite`, `Files.Read.All`, `Files.ReadWrite.All`, `offline_access`, `User.Read`, and optionally `Sites.Read.All` (see below). Once selected click `Add permissions` at the bottom.
+5. Search and select the following permissions: `Files.Read`, `Files.ReadWrite`, `Files.Read.All`, `Files.ReadWrite.All`, `offline_access`, `User.Read` and `Sites.Read.All` (if custom access scopes are configured, select the permissions accordingly). Once selected click `Add permissions` at the bottom.
Now the application is complete. Run `rclone config` to create or edit a OneDrive remote.
Supply the app ID and password as Client ID and Secret, respectively. rclone will walk you through the remaining steps.
-The `Sites.Read.All` permission is required if you need to [search SharePoint sites when configuring the remote](https://github.com/rclone/rclone/pull/5883). However, if that permission is not assigned, you need to set `disable_site_permission` option to true in the advanced options.
+The access_scopes option allows you to configure the permissions requested by rclone.
+See [Microsoft Docs](https://docs.microsoft.com/en-us/graph/permissions-reference#files-permissions) for more information about the different scopes.
+
+The `Sites.Read.All` permission is required if you need to [search SharePoint sites when configuring the remote](https://github.com/rclone/rclone/pull/5883). However, if that permission is not assigned, you need to exclude `Sites.Read.All` from your access scopes or set `disable_site_permission` option to true in the advanced options.
### Modification time and hashes
From 0279bf3abb93e86e5fda78ffea014adaac72ff81 Mon Sep 17 00:00:00 2001
From: CrossR
Date: Thu, 20 May 2021 20:39:04 +0100
Subject: [PATCH 039/560] ncdu: implement multi selection
Co-authored-by: buengese
---
cmd/ncdu/ncdu.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 102 insertions(+), 1 deletion(-)
diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go
index 4ef08f3468a3d..963ddb628df15 100644
--- a/cmd/ncdu/ncdu.go
+++ b/cmd/ncdu/ncdu.go
@@ -92,6 +92,9 @@ func helpText() (tr []string) {
" u toggle human-readable format",
" n,s,C,A sort by name,size,count,average size",
" d delete file/directory",
+ " v select file/directory",
+ " V enter visual select mode",
+ " D delete selected files/directories",
}
if !clipboard.Unsupported {
tr = append(tr, " y copy current path to clipboard")
@@ -126,11 +129,13 @@ type UI struct {
showCounts bool // toggle showing counts
showDirAverageSize bool // toggle average size
humanReadable bool // toggle human-readable format
+ visualSelectMode bool // toggle visual selection mode
sortByName int8 // +1 for normal, 0 for off, -1 for reverse
sortBySize int8
sortByCount int8
sortByAverageSize int8
dirPosMap map[string]dirPos // store for directory positions
+ selectedEntries map[string]dirPos // selected entries of current directory
}
// Where we have got to in the directory listing
@@ -361,6 +366,7 @@ func (u *UI) Draw() error {
break
}
attrs, err := u.d.AttrI(u.sortPerm[n])
+ _, isSelected := u.selectedEntries[entry.String()]
fg := termbox.ColorWhite
if attrs.EntriesHaveErrors {
fg = termbox.ColorYellow
@@ -368,6 +374,9 @@ func (u *UI) Draw() error {
if err != nil {
fg = termbox.ColorRed
}
+ if isSelected {
+ fg = termbox.ColorLightYellow
+ }
bg := termbox.ColorBlack
if n == dirPos.entry {
fg, bg = bg, fg
@@ -494,6 +503,11 @@ func (u *UI) move(d int) {
dirPos.offset = entries - 1
}
+ // toggle the current file for selection in selection mode
+ if u.visualSelectMode {
+ u.toggleSelectForCursor()
+ }
+
// write dirPos back for later
u.dirPosMap[u.path] = dirPos
}
@@ -503,11 +517,19 @@ func (u *UI) removeEntry(pos int) {
u.setCurrentDir(u.d)
}
-// delete the entry at the current position
func (u *UI) delete() {
if u.d == nil || len(u.entries) == 0 {
return
}
+ if len(u.selectedEntries) > 0 {
+ u.deleteSelected()
+ } else {
+ u.deleteSingle()
+ }
+}
+
+// delete the entry at the current position
+func (u *UI) deleteSingle() {
ctx := context.Background()
cursorPos := u.dirPosMap[u.path]
dirPos := u.sortPerm[cursorPos.entry]
@@ -553,6 +575,62 @@ func (u *UI) delete() {
}
}
+func (u *UI) deleteSelected() {
+ ctx := context.Background()
+
+ u.boxMenu = []string{"cancel", "confirm"}
+
+ u.boxMenuHandler = func(f fs.Fs, p string, o int) (string, error) {
+ if o != 1 {
+ return "Aborted!", nil
+ }
+
+ positionsToDelete := make([]int, len(u.selectedEntries))
+ i := 0
+
+ for key, cursorPos := range u.selectedEntries {
+
+ dirPos := u.sortPerm[cursorPos.entry]
+ dirEntry := u.entries[dirPos]
+ var err error
+
+ if obj, isFile := dirEntry.(fs.Object); isFile {
+ err = operations.DeleteFile(ctx, obj)
+ } else {
+ err = operations.Purge(ctx, f, dirEntry.String())
+ }
+
+ if err != nil {
+ return "", err
+ }
+
+ delete(u.selectedEntries, key)
+ positionsToDelete[i] = dirPos
+ i++
+ }
+
+ // deleting all entries at once, as doing it during the deletions
+ // could cause issues.
+ sort.Slice(positionsToDelete, func(i, j int) bool {
+ return positionsToDelete[i] > positionsToDelete[j]
+ })
+ for _, dirPos := range positionsToDelete {
+ u.removeEntry(dirPos)
+ }
+
+ // move cursor at end if needed
+ cursorPos := u.dirPosMap[u.path]
+ if cursorPos.entry >= len(u.entries) {
+ u.move(-1)
+ }
+
+ return "Successfully deleted all items!", nil
+ }
+ u.popupBox([]string{
+ "Delete selected items?",
+ fmt.Sprintf("ALL %d items will be deleted", len(u.selectedEntries))})
+}
+
func (u *UI) displayPath() {
u.togglePopupBox([]string{
"Current Path",
@@ -661,6 +739,8 @@ func (u *UI) setCurrentDir(d *scan.Dir) {
u.d = d
u.entries = d.Entries()
u.path = fspath.JoinRootPath(u.fsName, d.Path())
+ u.selectedEntries = make(map[string]dirPos)
+ u.visualSelectMode = false
u.sortCurrentDir()
}
@@ -737,6 +817,20 @@ func (u *UI) toggleSort(sortType *int8) {
u.sortCurrentDir()
}
+func (u *UI) toggleSelectForCursor() {
+ cursorPos := u.dirPosMap[u.path]
+ dirPos := u.sortPerm[cursorPos.entry]
+ dirEntry := u.entries[dirPos]
+
+ _, present := u.selectedEntries[dirEntry.String()]
+
+ if present {
+ delete(u.selectedEntries, dirEntry.String())
+ } else {
+ u.selectedEntries[dirEntry.String()] = cursorPos
+ }
+}
+
// NewUI creates a new user interface for ncdu on f
func NewUI(f fs.Fs) *UI {
return &UI{
@@ -752,6 +846,7 @@ func NewUI(f fs.Fs) *UI {
sortBySize: 1,
sortByCount: 0,
dirPosMap: make(map[string]dirPos),
+ selectedEntries: make(map[string]dirPos),
}
}
@@ -845,6 +940,10 @@ outer:
u.toggleSort(&u.sortByName)
case 's':
u.toggleSort(&u.sortBySize)
+ case 'v':
+ u.toggleSelectForCursor()
+ case 'V':
+ u.visualSelectMode = !u.visualSelectMode
case 'C':
u.toggleSort(&u.sortByCount)
case 'A':
@@ -857,6 +956,8 @@ outer:
u.delete()
case 'u':
u.humanReadable = !u.humanReadable
+ case 'D':
+ u.deleteSelected()
case '?':
u.togglePopupBox(helpText())
From 621c4ebe15600635a2d085a55b521fe39cea2511 Mon Sep 17 00:00:00 2001
From: buengese
Date: Wed, 15 Jun 2022 15:01:16 +0200
Subject: [PATCH 040/560] bin/make_backend_docs: allow generation of docs for
just one backend
---
bin/make_backend_docs.py | 31 ++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/bin/make_backend_docs.py b/bin/make_backend_docs.py
index 8753061f94bb6..1871cc68bd044 100755
--- a/bin/make_backend_docs.py
+++ b/bin/make_backend_docs.py
@@ -3,9 +3,11 @@
Make backend documentation
"""
+import sys
import os
import io
import subprocess
+from pathlib import Path
marker = "{{< rem autogenerated options"
start = marker + " start"
@@ -16,18 +18,19 @@ def find_backends():
"""Return a list of all backends"""
return [ x for x in os.listdir("backend") if x not in ("all",) ]
-def output_docs(backend, out):
+def output_docs(backend, out, cwd):
"""Output documentation for backend options to out"""
out.flush()
- subprocess.check_call(["rclone", "help", "backend", backend], stdout=out)
+ subprocess.check_call(["./rclone", "help", "backend", backend], stdout=out)
-def output_backend_tool_docs(backend, out):
+def output_backend_tool_docs(backend, out, cwd):
"""Output documentation for backend tool to out"""
out.flush()
- subprocess.call(["rclone", "backend", "help", backend], stdout=out, stderr=subprocess.DEVNULL)
+ subprocess.call(["./rclone", "backend", "help", backend], stdout=out, stderr=subprocess.DEVNULL)
def alter_doc(backend):
"""Alter the documentation for backend"""
+ rclone_bin_dir = Path(sys.path[0]).parent.absolute()
doc_file = "docs/content/"+backend+".md"
if not os.path.exists(doc_file):
raise ValueError("Didn't find doc file %s" % (doc_file,))
@@ -41,8 +44,8 @@ def alter_doc(backend):
in_docs = True
start_full = (start + "\" - DO NOT EDIT - instead edit fs.RegInfo in backend/%s/%s.go then run make backenddocs\" " + end + "\n") % (backend, backend)
out_file.write(start_full)
- output_docs(backend, out_file)
- output_backend_tool_docs(backend, out_file)
+ output_docs(backend, out_file, rclone_bin_dir)
+ output_backend_tool_docs(backend, out_file, rclone_bin_dir)
out_file.write(stop+" "+end+"\n")
altered = True
if not in_docs:
@@ -55,7 +58,18 @@ def alter_doc(backend):
if not altered:
raise ValueError("Didn't find '%s' markers for in %s" % (start, doc_file))
-if __name__ == "__main__":
+
+def main(args):
+ # single backend
+ if (len(args) == 2):
+ try:
+ alter_doc(args[1])
+ print("Added docs for %s backend" % args[1])
+ except Exception as e:
+ print("Failed adding docs for %s backend: %s" % (args[1], e))
+ return
+
+ # all backends
failed, success = 0, 0
for backend in find_backends():
try:
@@ -66,3 +80,6 @@ def alter_doc(backend):
else:
success += 1
print("Added docs for %d backends with %d failures" % (success, failed))
+
+if __name__ == "__main__":
+ main(sys.argv)
From 32f913ffbdd6c6f447584eed68a5a9bf09d220e0 Mon Sep 17 00:00:00 2001
From: buengese
Date: Tue, 14 Jun 2022 14:34:37 +0200
Subject: [PATCH 041/560] pcloud: fix cleanup - fixes #3853
---
backend/pcloud/pcloud.go | 28 +++++++++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/backend/pcloud/pcloud.go b/backend/pcloud/pcloud.go
index a73593b4059da..c558e4495a066 100644
--- a/backend/pcloud/pcloud.go
+++ b/backend/pcloud/pcloud.go
@@ -24,6 +24,7 @@ import (
"github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/fserrors"
+ "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/dircache"
@@ -130,6 +131,19 @@ with rclone authorize.
Value: "eapi.pcloud.com",
Help: "EU region",
}},
+ }, {
+ Name: "username",
+ Help: `Your pcloud username.
+
+This is only required when you want to use the cleanup command. Due to a bug
+in the pcloud API the required API does not support OAuth authentication so
+we have to rely on user password authentication for it.`,
+ Advanced: true,
+ }, {
+ Name: "password",
+ Help: "Your pcloud password.",
+ IsPassword: true,
+ Advanced: true,
}}...),
})
}
@@ -139,6 +153,8 @@ type Options struct {
Enc encoder.MultiEncoder `config:"encoding"`
RootFolderID string `config:"root_folder_id"`
Hostname string `config:"hostname"`
+ Username string `config:"username"`
+ Password string `config:"password"`
}
// Fs represents a remote pcloud
@@ -148,6 +164,7 @@ type Fs struct {
opt Options // parsed options
features *fs.Features // optional features
srv *rest.Client // the connection to the server
+ cleanupSrv *rest.Client // the connection used for the cleanup method
dirCache *dircache.DirCache // Map of directory path to directory id
pacer *fs.Pacer // pacer for API calls
tokenRenewer *oauthutil.Renew // renew the token on expiry
@@ -293,6 +310,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
}
updateTokenURL(oauthConfig, opt.Hostname)
+ canCleanup := opt.Username != "" && opt.Password != ""
f := &Fs{
name: name,
root: root,
@@ -300,10 +318,16 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
srv: rest.NewClient(oAuthClient).SetRoot("https://" + opt.Hostname),
pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
}
+ if canCleanup {
+ f.cleanupSrv = rest.NewClient(fshttp.NewClient(ctx)).SetRoot("https://" + opt.Hostname)
+ }
f.features = (&fs.Features{
CaseInsensitive: false,
CanHaveEmptyDirectories: true,
}).Fill(ctx, f)
+ if !canCleanup {
+ f.features.CleanUp = nil
+ }
f.srv.SetErrorHandler(errorHandler)
// Renew the token in the background
@@ -729,10 +753,12 @@ func (f *Fs) CleanUp(ctx context.Context) error {
Parameters: url.Values{},
}
opts.Parameters.Set("folderid", dirIDtoNumber(rootID))
+ opts.Parameters.Set("username", f.opt.Username)
+ opts.Parameters.Set("password", obscure.MustReveal(f.opt.Password))
var resp *http.Response
var result api.Error
return f.pacer.Call(func() (bool, error) {
- resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
+ resp, err = f.cleanupSrv.CallJSON(ctx, &opts, nil, &result)
err = result.Update(err)
return shouldRetry(ctx, resp, err)
})
From 93a25498cfed086f9df7970d987abd24762d49b8 Mon Sep 17 00:00:00 2001
From: buengese
Date: Wed, 15 Jun 2022 16:44:13 +0200
Subject: [PATCH 042/560] docs/pcloud: document the cleanup issues
---
docs/content/pcloud.md | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/docs/content/pcloud.md b/docs/content/pcloud.md
index 4af0c968b26da..eb11905dd5987 100644
--- a/docs/content/pcloud.md
+++ b/docs/content/pcloud.md
@@ -112,6 +112,13 @@ Deleted files will be moved to the trash. Your subscription level
will determine how long items stay in the trash. `rclone cleanup` can
be used to empty the trash.
+### Emptying the trash
+
+Due to an API limitation, the `rclone cleanup` command will only work if you
+set your username and password in the advanced options for this backend.
+Since we generally want to avoid storing user passwords in the rclone config
+file, we advise you to only set this up if you need the `rclone cleanup` command to work.
+
### Root folder ID
You can set the `root_folder_id` for rclone. This is the directory
@@ -251,4 +258,32 @@ Properties:
- "eapi.pcloud.com"
- EU region
+#### --pcloud-username
+
+Your pcloud username.
+
+This is only required when you want to use the cleanup command. Due to a bug
+in the pcloud API the required API does not support OAuth authentication so
+we have to rely on user password authentication for it.
+
+Properties:
+
+- Config: username
+- Env Var: RCLONE_PCLOUD_USERNAME
+- Type: string
+- Required: false
+
+#### --pcloud-password
+
+Your pcloud password.
+
+**NB** Input to this must be obscured - see [rclone obscure](/commands/rclone_obscure/).
+
+Properties:
+
+- Config: password
+- Env Var: RCLONE_PCLOUD_PASSWORD
+- Type: string
+- Required: false
+
{{< rem autogenerated options stop >}}
From 592358148da684c087cb79d6c0f135c667b88d30 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 16:47:47 +0100
Subject: [PATCH 043/560] Add m00594701 to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 8d5cbbac44fbe..a34d9b2f605d5 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -606,3 +606,4 @@ put them back in again.` >}}
* Jason Zheng
* Matthew Vernon
* Noah Hsu
+ * m00594701
From 41f3ceb67df596f5af607c9c5e3dfb56d1c85b25 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 16:47:47 +0100
Subject: [PATCH 044/560] Add Art M. Gallagher to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index a34d9b2f605d5..1316c13ec09bd 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -607,3 +607,4 @@ put them back in again.` >}}
* Matthew Vernon
* Noah Hsu
* m00594701
+ * Art M. Gallagher
From c390098262bd4da9c50967e83514203ed7023845 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 16:47:47 +0100
Subject: [PATCH 045/560] Add Sven Gerber to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 1316c13ec09bd..23316188995ec 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -608,3 +608,4 @@ put them back in again.` >}}
* Noah Hsu
* m00594701
* Art M. Gallagher
+ * Sven Gerber <49589423+svengerber@users.noreply.github.com>
From 6c832a72ee9bd3500d01f8232da0241cb24dc4f5 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 16:47:47 +0100
Subject: [PATCH 046/560] Add CrossR to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 23316188995ec..91fe78693606b 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -609,3 +609,4 @@ put them back in again.` >}}
* m00594701
* Art M. Gallagher
* Sven Gerber <49589423+svengerber@users.noreply.github.com>
+ * CrossR
From 626a416ff8946b4fcc86e34780c90d64d1c481b8 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 11:11:14 +0100
Subject: [PATCH 047/560] vfs: factor out the VFS option initialization for
re-use #3259
---
vfs/vfs.go | 8 ++------
vfs/vfscommon/options.go | 11 +++++++++++
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/vfs/vfs.go b/vfs/vfs.go
index d220d015e2091..fc4eb126442a1 100644
--- a/vfs/vfs.go
+++ b/vfs/vfs.go
@@ -193,12 +193,8 @@ func New(f fs.Fs, opt *vfscommon.Options) *VFS {
vfs.Opt = vfscommon.DefaultOpt
}
- // Mask the permissions with the umask
- vfs.Opt.DirPerms &= ^os.FileMode(vfs.Opt.Umask)
- vfs.Opt.FilePerms &= ^os.FileMode(vfs.Opt.Umask)
-
- // Make sure directories are returned as directories
- vfs.Opt.DirPerms |= os.ModeDir
+ // Fill out anything else
+ vfs.Opt.Init()
// Find a VFS with the same name and options and return it if possible
activeMu.Lock()
diff --git a/vfs/vfscommon/options.go b/vfs/vfscommon/options.go
index 90cbe5bb7f33d..5c1ae7900e669 100644
--- a/vfs/vfscommon/options.go
+++ b/vfs/vfscommon/options.go
@@ -62,3 +62,14 @@ var DefaultOpt = Options{
ReadAhead: 0 * fs.Mebi,
UsedIsSize: false,
}
+
+// Init the options, making sure everything is withing range
+func (opt *Options) Init() {
+ // Mask the permissions with the umask
+ opt.DirPerms &= ^os.FileMode(opt.Umask)
+ opt.FilePerms &= ^os.FileMode(opt.Umask)
+
+ // Make sure directories are returned as directories
+ opt.DirPerms |= os.ModeDir
+
+}
From 4a382c09ecab5cee85726d7b4215bb502b7c65b9 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 12:08:11 +0100
Subject: [PATCH 048/560] mount: run tests in a subprocess to fix deadlock -
fixes #3259
Before this change we ran the tests and the mount in the same process.
This could cause deadlocks and often did, and made the mount tests
very unreliable.
This fixes the problem by running the mount in a seperate process and
commanding it via a pipe over stdin/stdout.
---
vfs/vfstest/dir.go | 8 +-
vfs/vfstest/fs.go | 190 +++++++-------------------
vfs/vfstest/submount.go | 276 ++++++++++++++++++++++++++++++++++++++
vfs/vfstest/write.go | 4 +-
vfs/vfstest/write_unix.go | 2 +-
5 files changed, 327 insertions(+), 153 deletions(-)
create mode 100644 vfs/vfstest/submount.go
diff --git a/vfs/vfstest/dir.go b/vfs/vfstest/dir.go
index 8c6273a1c5a11..0cf02ed0f8814 100644
--- a/vfs/vfstest/dir.go
+++ b/vfs/vfstest/dir.go
@@ -5,7 +5,6 @@ import (
"testing"
"time"
- "github.com/rclone/rclone/fs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -175,15 +174,12 @@ func TestDirCacheFlush(t *testing.T) {
err := run.fremote.Mkdir(context.Background(), "dir/subdir")
require.NoError(t, err)
- root, err := run.vfs.Root()
- require.NoError(t, err)
-
// expect newly created "subdir" on remote to not show up
- root.ForgetPath("otherdir", fs.EntryDirectory)
+ run.forget("otherdir")
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
- root.ForgetPath("dir", fs.EntryDirectory)
+ run.forget("dir")
dm = newDirMap("otherdir/|otherdir/file 1|dir/|dir/file 1|dir/subdir/")
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
diff --git a/vfs/vfstest/fs.go b/vfs/vfstest/fs.go
index bb9a61fc50ed4..6781fbe312596 100644
--- a/vfs/vfstest/fs.go
+++ b/vfs/vfstest/fs.go
@@ -3,11 +3,11 @@
package vfstest
import (
+ "bufio"
"context"
"flag"
"fmt"
"io"
- "io/ioutil"
"log"
"os"
"os/exec"
@@ -16,6 +16,7 @@ import (
"reflect"
"runtime"
"strings"
+ "sync"
"testing"
"time"
@@ -24,8 +25,6 @@ import (
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/fstest"
- "github.com/rclone/rclone/lib/file"
- "github.com/rclone/rclone/vfs"
"github.com/rclone/rclone/vfs/vfscommon"
"github.com/rclone/rclone/vfs/vfsflags"
"github.com/stretchr/testify/assert"
@@ -36,16 +35,19 @@ const (
waitForWritersDelay = 30 * time.Second // time to wait for existing writers
)
-var (
- mountFn mountlib.MountFn
-)
-
// RunTests runs all the tests against all the VFS cache modes
//
-// If useVFS is set then it runs the tests against a VFS rather than amount
-func RunTests(t *testing.T, useVFS bool, fn mountlib.MountFn) {
- mountFn = fn
+// If useVFS is set then it runs the tests against a VFS rather than a
+// mount
+//
+// If useVFS is not set then it runs the mount in a subprocess in
+// order to avoid kernel deadlocks.
+func RunTests(t *testing.T, useVFS bool, mountFn mountlib.MountFn) {
flag.Parse()
+ if isSubProcess() {
+ startMount(mountFn, useVFS, *runMount)
+ return
+ }
tests := []struct {
cacheMode vfscommon.CacheMode
writeBack time.Duration
@@ -56,9 +58,11 @@ func RunTests(t *testing.T, useVFS bool, fn mountlib.MountFn) {
{cacheMode: vfscommon.CacheModeFull},
{cacheMode: vfscommon.CacheModeFull, writeBack: 100 * time.Millisecond},
}
- run = newRun(useVFS)
for _, test := range tests {
- run.cacheMode(test.cacheMode, test.writeBack)
+ vfsOpt := vfsflags.Opt
+ vfsOpt.CacheMode = test.cacheMode
+ vfsOpt.WriteBack = test.writeBack
+ run = newRun(useVFS, &vfsOpt, mountFn)
what := fmt.Sprintf("CacheMode=%v", test.cacheMode)
if test.writeBack > 0 {
what += fmt.Sprintf(",WriteBack=%v", test.writeBack)
@@ -93,24 +97,29 @@ func RunTests(t *testing.T, useVFS bool, fn mountlib.MountFn) {
t.Run("TestWriteFileAppend", TestWriteFileAppend)
})
log.Printf("Finished test run with %s (ok=%v)", what, ok)
+ run.Finalise()
if !ok {
break
}
}
- run.Finalise()
}
// Run holds the remotes for a test run
type Run struct {
os Oser
- vfs *vfs.VFS
+ vfsOpt *vfscommon.Options
useVFS bool // set if we are testing a VFS not a mount
- mnt *mountlib.MountPoint
mountPath string
fremote fs.Fs
fremoteName string
cleanRemote func()
skip bool
+ // For controlling the subprocess running the mount
+ cmdMu sync.Mutex
+ cmd *exec.Cmd
+ in io.ReadCloser
+ out io.WriteCloser
+ scanner *bufio.Scanner
}
// run holds the master Run data
@@ -122,10 +131,12 @@ var run *Run
// r.fremote is an empty remote Fs
//
// Finalise() will tidy them away when done.
-func newRun(useVFS bool) *Run {
+func newRun(useVFS bool, vfsOpt *vfscommon.Options, mountFn mountlib.MountFn) *Run {
r := &Run{
useVFS: useVFS,
+ vfsOpt: vfsOpt,
}
+ r.vfsOpt.Init()
fstest.Initialise()
var err error
@@ -139,118 +150,10 @@ func newRun(useVFS bool) *Run {
log.Fatalf("Failed to open mkdir %q: %v", *fstest.RemoteName, err)
}
- if !r.useVFS {
- r.mountPath = findMountPath()
- }
- // Mount it up
- r.mount()
-
+ r.startMountSubProcess()
return r
}
-func findMountPath() string {
- if runtime.GOOS != "windows" {
- mountPath, err := ioutil.TempDir("", "rclonefs-mount")
- if err != nil {
- log.Fatalf("Failed to create mount dir: %v", err)
- }
- return mountPath
- }
-
- // Find a free drive letter
- letter := file.FindUnusedDriveLetter()
- drive := ""
- if letter == 0 {
- log.Fatalf("Couldn't find free drive letter for test")
- } else {
- drive = string(letter) + ":"
- }
- return drive
-}
-
-func (r *Run) mount() {
- log.Printf("mount %q %q", r.fremote, r.mountPath)
- var err error
- r.mnt = mountlib.NewMountPoint(mountFn, r.mountPath, r.fremote, &mountlib.Opt, &vfsflags.Opt)
-
- _, err = r.mnt.Mount()
- if err != nil {
- log.Printf("mount FAILED: %v", err)
- r.skip = true
- } else {
- log.Printf("mount OK")
- }
- r.vfs = r.mnt.VFS
- if r.useVFS {
- r.os = vfsOs{r.vfs}
- } else {
- r.os = realOs{}
- }
-
-}
-
-func (r *Run) umount() {
- if r.skip {
- log.Printf("FUSE not found so skipping umount")
- return
- }
- /*
- log.Printf("Calling fusermount -u %q", r.mountPath)
- err := exec.Command("fusermount", "-u", r.mountPath).Run()
- if err != nil {
- log.Printf("fusermount failed: %v", err)
- }
- */
- log.Printf("Unmounting %q", r.mountPath)
- err := r.mnt.Unmount()
- if err != nil {
- log.Printf("signal to umount failed - retrying: %v", err)
- time.Sleep(3 * time.Second)
- err = r.mnt.Unmount()
- }
- if err != nil {
- log.Fatalf("signal to umount failed: %v", err)
- }
- log.Printf("Waiting for umount")
- err = <-r.mnt.ErrChan
- if err != nil {
- log.Fatalf("umount failed: %v", err)
- }
-
- // Cleanup the VFS cache - umount has called Shutdown
- err = r.vfs.CleanUp()
- if err != nil {
- log.Printf("Failed to cleanup the VFS cache: %v", err)
- }
-}
-
-// cacheMode flushes the VFS and changes the CacheMode and the writeBack time
-func (r *Run) cacheMode(cacheMode vfscommon.CacheMode, writeBack time.Duration) {
- if r.skip {
- log.Printf("FUSE not found so skipping cacheMode")
- return
- }
- // Wait for writers to finish
- r.vfs.WaitForWriters(waitForWritersDelay)
- // Empty and remake the remote
- r.cleanRemote()
- err := r.fremote.Mkdir(context.Background(), "")
- if err != nil {
- log.Fatalf("Failed to open mkdir %q: %v", *fstest.RemoteName, err)
- }
- // Empty the cache
- err = r.vfs.CleanUp()
- if err != nil {
- log.Printf("Failed to cleanup the VFS cache: %v", err)
- }
- // Reset the cache mode
- r.vfs.SetCacheMode(cacheMode)
- r.vfs.Opt.WriteBack = writeBack
- // Flush the directory cache
- r.vfs.FlushDirCache()
-
-}
-
func (r *Run) skipIfNoFUSE(t *testing.T) {
if r.skip {
t.Skip("FUSE not found so skipping test")
@@ -265,11 +168,15 @@ func (r *Run) skipIfVFS(t *testing.T) {
// Finalise cleans the remote and unmounts
func (r *Run) Finalise() {
- r.umount()
+ if !r.useVFS {
+ r.sendMountCommand("exit")
+ _, err := r.cmd.Process.Wait()
+ if err != nil {
+ log.Fatalf("mount sub process failed: %v", err)
+ }
+ }
r.cleanRemote()
- if r.useVFS {
- // FIXME
- } else {
+ if !r.useVFS {
err := os.RemoveAll(r.mountPath)
if err != nil {
log.Printf("Failed to clean mountPath %q: %v", r.mountPath, err)
@@ -284,9 +191,9 @@ func (r *Run) path(filePath string) string {
}
// return windows drive letter root as E:\
if filePath == "" && runtime.GOOS == "windows" {
- return run.mountPath + `\`
+ return r.mountPath + `\`
}
- return filepath.Join(run.mountPath, filepath.FromSlash(filePath))
+ return filepath.Join(r.mountPath, filepath.FromSlash(filePath))
}
type dirMap map[string]struct{}
@@ -323,10 +230,10 @@ func (r *Run) readLocal(t *testing.T, dir dirMap, filePath string) {
if fi.IsDir() {
dir[name+"/"] = struct{}{}
r.readLocal(t, dir, name)
- assert.Equal(t, run.vfs.Opt.DirPerms&os.ModePerm, fi.Mode().Perm())
+ assert.Equal(t, r.vfsOpt.DirPerms&os.ModePerm, fi.Mode().Perm())
} else {
dir[fmt.Sprintf("%s %d", name, fi.Size())] = struct{}{}
- assert.Equal(t, run.vfs.Opt.FilePerms&os.ModePerm, fi.Mode().Perm())
+ assert.Equal(t, r.vfsOpt.FilePerms&os.ModePerm, fi.Mode().Perm())
}
}
}
@@ -374,11 +281,6 @@ func (r *Run) checkDir(t *testing.T, dirString string) {
assert.Equal(t, dm, localDm, "expected vs fuse mount")
}
-// wait for any files being written to be released by fuse
-func (r *Run) waitForWriters() {
- run.vfs.WaitForWriters(waitForWritersDelay)
-}
-
// writeFile writes data to a file named by filename.
// If the file does not exist, WriteFile creates it with permissions perm;
// otherwise writeFile truncates it before writing.
@@ -415,25 +317,25 @@ func (r *Run) createFile(t *testing.T, filepath string, contents string) {
func (r *Run) readFile(t *testing.T, filepath string) string {
filepath = r.path(filepath)
- result, err := run.os.ReadFile(filepath)
+ result, err := r.os.ReadFile(filepath)
require.NoError(t, err)
return string(result)
}
func (r *Run) mkdir(t *testing.T, filepath string) {
filepath = r.path(filepath)
- err := run.os.Mkdir(filepath, 0700)
+ err := r.os.Mkdir(filepath, 0700)
require.NoError(t, err)
}
func (r *Run) rm(t *testing.T, filepath string) {
filepath = r.path(filepath)
- err := run.os.Remove(filepath)
+ err := r.os.Remove(filepath)
require.NoError(t, err)
// Wait for file to disappear from listing
for i := 0; i < 100; i++ {
- _, err := run.os.Stat(filepath)
+ _, err := r.os.Stat(filepath)
if os.IsNotExist(err) {
return
}
@@ -444,7 +346,7 @@ func (r *Run) rm(t *testing.T, filepath string) {
func (r *Run) rmdir(t *testing.T, filepath string) {
filepath = r.path(filepath)
- err := run.os.Remove(filepath)
+ err := r.os.Remove(filepath)
require.NoError(t, err)
}
@@ -470,5 +372,5 @@ func TestRoot(t *testing.T) {
fi, err := os.Lstat(run.mountPath)
require.NoError(t, err)
assert.True(t, fi.IsDir())
- assert.Equal(t, run.vfs.Opt.DirPerms&os.ModePerm, fi.Mode().Perm())
+ assert.Equal(t, run.vfsOpt.DirPerms&os.ModePerm, fi.Mode().Perm())
}
diff --git a/vfs/vfstest/submount.go b/vfs/vfstest/submount.go
new file mode 100644
index 0000000000000..e405dd847658b
--- /dev/null
+++ b/vfs/vfstest/submount.go
@@ -0,0 +1,276 @@
+package vfstest
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/rclone/rclone/cmd/mountlib"
+ "github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fs/cache"
+ "github.com/rclone/rclone/fstest"
+ "github.com/rclone/rclone/lib/file"
+ "github.com/rclone/rclone/vfs"
+ "github.com/rclone/rclone/vfs/vfscommon"
+)
+
+// Functions to run and control the mount subprocess
+
+var (
+ runMount = flag.String("run-mount", "", "If set, run the mount subprocess with the options (internal use only)")
+)
+
+// Options for the mount sub processes passed with the -run-mount flag
+type runMountOpt struct {
+ MountPoint string
+ MountOpt mountlib.Options
+ VFSOpt vfscommon.Options
+ Remote string
+}
+
+// Start the mount subprocess and wait for it to start
+func (r *Run) startMountSubProcess() {
+ // If testing the VFS we don't start a subprocess, we just use
+ // the VFS directly
+ if r.useVFS {
+ vfs := vfs.New(r.fremote, r.vfsOpt)
+ r.os = vfsOs{vfs}
+ return
+ }
+ r.os = realOs{}
+ r.mountPath = findMountPath()
+ log.Printf("startMountSubProcess %q (%q) %q", r.fremote, r.fremoteName, r.mountPath)
+
+ opt := runMountOpt{
+ MountPoint: r.mountPath,
+ MountOpt: mountlib.Opt,
+ VFSOpt: *r.vfsOpt,
+ Remote: r.fremoteName,
+ }
+
+ opts, err := json.Marshal(&opt)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Re-run this executable with a new option -run-mount
+ args := append(os.Args, "-run-mount", string(opts))
+ r.cmd = exec.Command(args[0], args[1:]...)
+ r.cmd.Stderr = os.Stderr
+ r.out, err = r.cmd.StdinPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ r.in, err = r.cmd.StdoutPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = r.cmd.Start()
+ if err != nil {
+ log.Fatal("startMountSubProcess failed", err)
+ }
+ r.scanner = bufio.NewScanner(r.in)
+
+ // Wait it for startup
+ log.Print("Waiting for mount to start")
+ for r.scanner.Scan() {
+ rx := strings.TrimSpace(r.scanner.Text())
+ if rx == "STARTED" {
+ break
+ }
+ log.Printf("..Mount said: %s", rx)
+ }
+ if r.scanner.Err() != nil {
+ log.Printf("scanner err %v", r.scanner.Err())
+ }
+
+ log.Printf("startMountSubProcess: end")
+}
+
+// Find a free path to run the mount on
+func findMountPath() string {
+ if runtime.GOOS != "windows" {
+ mountPath, err := ioutil.TempDir("", "rclonefs-mount")
+ if err != nil {
+ log.Fatalf("Failed to create mount dir: %v", err)
+ }
+ return mountPath
+ }
+
+ // Find a free drive letter
+ letter := file.FindUnusedDriveLetter()
+ drive := ""
+ if letter == 0 {
+ log.Fatalf("Couldn't find free drive letter for test")
+ } else {
+ drive = string(letter) + ":"
+ }
+ return drive
+}
+
+// Return true if we are running as a subprocess to run the mount
+func isSubProcess() bool {
+ return *runMount != ""
+}
+
+// Run the mount - this is running in a subprocesses and the config
+// is passed JSON encoded as the -run-mount parameter
+//
+// It reads commands from standard input and writes results to
+// standard output.
+func startMount(mountFn mountlib.MountFn, useVFS bool, opts string) {
+ log.Print("startMount")
+ ctx := context.Background()
+
+ var opt runMountOpt
+ err := json.Unmarshal([]byte(opts), &opt)
+ if err != nil {
+ log.Fatalf("Unmarshal failed: %v", err)
+ }
+
+ fstest.Initialise()
+
+ f, err := cache.Get(ctx, opt.Remote)
+ if err != nil {
+ log.Fatalf("Failed to open remote %q: %v", opt.Remote, err)
+ }
+
+ err = f.Mkdir(ctx, "")
+ if err != nil {
+ log.Fatalf("Failed to mkdir %q: %v", opt.Remote, err)
+ }
+
+ log.Printf("startMount: Mounting %q on %q with %q", opt.Remote, opt.MountPoint, opt.VFSOpt.CacheMode)
+ mnt := mountlib.NewMountPoint(mountFn, opt.MountPoint, f, &opt.MountOpt, &opt.VFSOpt)
+
+ _, err = mnt.Mount()
+ if err != nil {
+ log.Fatalf("mount FAILED %q: %v", opt.Remote, err)
+ }
+ defer umount(mnt)
+ log.Printf("startMount: mount OK")
+ fmt.Println("STARTED") // signal to parent all is good
+
+ // Read commands from stdin
+ scanner := bufio.NewScanner(os.Stdin)
+ exit := false
+ for !exit && scanner.Scan() {
+ rx := strings.Trim(scanner.Text(), "\r\n")
+ var tx string
+ tx, exit = doMountCommand(mnt.VFS, rx)
+ fmt.Println(tx)
+ }
+
+ err = scanner.Err()
+ if err != nil {
+ log.Fatalf("scanner failed %q: %v", opt.Remote, err)
+ }
+}
+
+// Do a mount command which is a line read from stdin and return a
+// line to send to stdout with an exit flag.
+//
+// The format of the lines is
+// command \t parameter (optional)
+// The response should be
+// OK|ERR \t result (optional)
+func doMountCommand(vfs *vfs.VFS, rx string) (tx string, exit bool) {
+ command := strings.Split(rx, "\t")
+ // log.Printf("doMountCommand: %q received", command)
+ var out = []string{"OK", ""}
+ switch command[0] {
+ case "waitForWriters":
+ vfs.WaitForWriters(waitForWritersDelay)
+ case "forget":
+ root, err := vfs.Root()
+ if err != nil {
+ out = []string{"ERR", err.Error()}
+ } else {
+ root.ForgetPath(command[1], fs.EntryDirectory)
+ }
+ case "exit":
+ exit = true
+ default:
+ out = []string{"ERR", "command not found"}
+ }
+ return strings.Join(out, "\t"), exit
+}
+
+// Send a command to the mount subprocess and await a response
+func (r *Run) sendMountCommand(args ...string) {
+ r.cmdMu.Lock()
+ defer r.cmdMu.Unlock()
+ tx := strings.Join(args, "\t")
+ // log.Printf("Send mount command: %q", tx)
+ var rx string
+ if r.useVFS {
+ // if using VFS do the VFS command directly
+ rx, _ = doMountCommand(r.os.(vfsOs).VFS, tx)
+ } else {
+ _, err := io.WriteString(r.out, tx+"\n")
+ if err != nil {
+ log.Fatalf("WriteString err %v", err)
+ }
+ if !r.scanner.Scan() {
+ log.Fatalf("Mount has gone away")
+ }
+ rx = strings.Trim(r.scanner.Text(), "\r\n")
+ }
+ in := strings.Split(rx, "\t")
+ // log.Printf("Answer is %q", in)
+ if in[0] != "OK" {
+ log.Fatalf("Error from mount: %q", in[1:])
+ }
+}
+
+// wait for any files being written to be released by fuse
+func (r *Run) waitForWriters() {
+ r.sendMountCommand("waitForWriters")
+}
+
+// forget the directory passed in
+func (r *Run) forget(dir string) {
+ r.sendMountCommand("forget", dir)
+}
+
+// Unmount the mount
+func umount(mnt *mountlib.MountPoint) {
+ /*
+ log.Printf("Calling fusermount -u %q", mountPath)
+ err := exec.Command("fusermount", "-u", mountPath).Run()
+ if err != nil {
+ log.Printf("fusermount failed: %v", err)
+ }
+ */
+ log.Printf("Unmounting %q", mnt.MountPoint)
+ err := mnt.Unmount()
+ if err != nil {
+ log.Printf("signal to umount failed - retrying: %v", err)
+ time.Sleep(3 * time.Second)
+ err = mnt.Unmount()
+ }
+ if err != nil {
+ log.Fatalf("signal to umount failed: %v", err)
+ }
+ log.Printf("Waiting for umount")
+ err = <-mnt.ErrChan
+ if err != nil {
+ log.Fatalf("umount failed: %v", err)
+ }
+
+ // Cleanup the VFS cache - umount has called Shutdown
+ err = mnt.VFS.CleanUp()
+ if err != nil {
+ log.Printf("Failed to cleanup the VFS cache: %v", err)
+ }
+}
diff --git a/vfs/vfstest/write.go b/vfs/vfstest/write.go
index 03ad7bfa1ae68..d4c2e0d92a5c6 100644
--- a/vfs/vfstest/write.go
+++ b/vfs/vfstest/write.go
@@ -91,7 +91,7 @@ func TestWriteFileDup(t *testing.T) {
run.skipIfVFS(t)
run.skipIfNoFUSE(t)
- if run.vfs.Opt.CacheMode < vfscommon.CacheModeWrites {
+ if run.vfsOpt.CacheMode < vfscommon.CacheModeWrites {
t.Skip("not supported on vfs-cache-mode < writes")
return
}
@@ -136,7 +136,7 @@ func TestWriteFileDup(t *testing.T) {
func TestWriteFileAppend(t *testing.T) {
run.skipIfNoFUSE(t)
- if run.vfs.Opt.CacheMode < vfscommon.CacheModeWrites {
+ if run.vfsOpt.CacheMode < vfscommon.CacheModeWrites {
t.Skip("not supported on vfs-cache-mode < writes")
return
}
diff --git a/vfs/vfstest/write_unix.go b/vfs/vfstest/write_unix.go
index 0d412be07469b..4ce40c970c70d 100644
--- a/vfs/vfstest/write_unix.go
+++ b/vfs/vfstest/write_unix.go
@@ -46,7 +46,7 @@ func TestWriteFileDoubleClose(t *testing.T) {
// write to the other dup
_, err = unix.Write(fd2, buf)
- if run.vfs.Opt.CacheMode < vfscommon.CacheModeWrites {
+ if run.vfsOpt.CacheMode < vfscommon.CacheModeWrites {
// produces an error if cache mode < writes
assert.Error(t, err, "input/output error")
} else {
From a0cb3bbd02e9d3a2843adac596c79732adfd144c Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 12:33:58 +0100
Subject: [PATCH 049/560] mount: allow tests to run on CI
---
cmd/mount/mount_test.go | 6 ------
1 file changed, 6 deletions(-)
diff --git a/cmd/mount/mount_test.go b/cmd/mount/mount_test.go
index 64f53eadcb841..ec5422cc9189b 100644
--- a/cmd/mount/mount_test.go
+++ b/cmd/mount/mount_test.go
@@ -4,17 +4,11 @@
package mount
import (
- "runtime"
"testing"
- "github.com/rclone/rclone/fstest/testy"
"github.com/rclone/rclone/vfs/vfstest"
)
func TestMount(t *testing.T) {
- if runtime.NumCPU() <= 2 {
- t.Skip("FIXME skipping mount tests as they lock up on <= 2 CPUs - See: https://github.com/rclone/rclone/issues/3154")
- }
- testy.SkipUnreliable(t)
vfstest.RunTests(t, false, mount)
}
From 2e91287b2eec71d8a57bd2b8e1958a9d9bf19d43 Mon Sep 17 00:00:00 2001
From: Maciej Radzikowski
Date: Thu, 16 Jun 2022 22:29:36 +0200
Subject: [PATCH 050/560] docs/s3: add note about chunk size decreasing
progress accuracy
---
backend/s3/s3.go | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index cd4f3e06b9ca3..ac5e112cb2c66 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -1677,7 +1677,14 @@ Files of unknown size are uploaded with the configured
chunk_size. Since the default chunk size is 5 MiB and there can be at
most 10,000 chunks, this means that by default the maximum size of
a file you can stream upload is 48 GiB. If you wish to stream upload
-larger files then you will need to increase chunk_size.`,
+larger files then you will need to increase chunk_size.
+
+Increasing the chunk size decreases the accuracy of the progress
+statistics displayed with "-P" flag. Rclone treats chunk as sent when
+it's buffered by the AWS SDK, when in fact it may still be uploading.
+A bigger chunk size means a bigger AWS SDK buffer and progress
+reporting more deviating from the truth.
+`,
Default: minChunkSize,
Advanced: true,
}, {
From e87e331f4c566ba96752a6dadbc7c99f377ab1ff Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 14 Jun 2022 09:44:37 +0100
Subject: [PATCH 051/560] drive: make --drive-shared-with-me work with shared
drives
Fixes #6247
---
backend/drive/drive.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index c5f5827f7b65b..176b94e6e890c 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -886,7 +886,7 @@ func (f *Fs) list(ctx context.Context, dirIDs []string, title string, directorie
}
list.SupportsAllDrives(true)
list.IncludeItemsFromAllDrives(true)
- if f.isTeamDrive {
+ if f.isTeamDrive && !f.opt.SharedWithMe {
list.DriveId(f.opt.TeamDriveID)
list.Corpora("drive")
}
From 411013dbdceb6168219639744dbcbbb24fc495ae Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 23 May 2022 17:24:53 +0100
Subject: [PATCH 052/560] drive: add --drive-resource-key for accessing
link-shared files
---
backend/drive/drive.go | 61 +++++++++++++++++++++++++++++++++++-------
1 file changed, 52 insertions(+), 9 deletions(-)
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index 176b94e6e890c..2a4d0a64b61b4 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -565,6 +565,27 @@ If this is set then rclone will not show any dangling shortcuts in listings.
`,
Advanced: true,
Default: false,
+ }, {
+ Name: "resource_key",
+ Help: `Resource key for accessing a link-shared file.
+
+If you need to access files shared with a link like this
+
+ https://drive.google.com/drive/folders/XXX?resourcekey=YYY&usp=sharing
+
+Then you will need to use the first part "XXX" as the "root_folder_id"
+and the second part "YYY" as the "resource_key" otherwise you will get
+404 not found errors when trying to access the directory.
+
+See: https://developers.google.com/drive/api/guides/resource-keys
+
+This resource key requirement only applies to a subset of old files.
+
+Note also that opening the folder once in the web interface (with the
+user you've authenticated rclone with) seems to be enough so that the
+resource key is no needed.
+`,
+ Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
@@ -626,6 +647,7 @@ type Options struct {
StopOnDownloadLimit bool `config:"stop_on_download_limit"`
SkipShortcuts bool `config:"skip_shortcuts"`
SkipDanglingShortcuts bool `config:"skip_dangling_shortcuts"`
+ ResourceKey string `config:"resource_key"`
Enc encoder.MultiEncoder `config:"encoding"`
}
@@ -651,6 +673,7 @@ type Fs struct {
grouping int32 // number of IDs to search at once in ListR - read with atomic
listRmu *sync.Mutex // protects listRempties
listRempties map[string]struct{} // IDs of supposedly empty directories which triggered grouping disable
+ dirResourceKeys *sync.Map // map directory ID to resource key
}
type baseObject struct {
@@ -802,6 +825,7 @@ func (f *Fs) list(ctx context.Context, dirIDs []string, title string, directorie
// We must not filter with parent when we try list "ROOT" with drive-shared-with-me
// If we need to list file inside those shared folders, we must search it without sharedWithMe
parentsQuery := bytes.NewBufferString("(")
+ var resourceKeys []string
for _, dirID := range dirIDs {
if dirID == "" {
continue
@@ -822,7 +846,12 @@ func (f *Fs) list(ctx context.Context, dirIDs []string, title string, directorie
} else {
_, _ = fmt.Fprintf(parentsQuery, "'%s' in parents", dirID)
}
+ resourceKey, hasResourceKey := f.dirResourceKeys.Load(dirID)
+ if hasResourceKey {
+ resourceKeys = append(resourceKeys, fmt.Sprintf("%s/%s", dirID, resourceKey))
+ }
}
+ resourceKeysHeader := strings.Join(resourceKeys, ",")
if parentsQuery.Len() > 1 {
_ = parentsQuery.WriteByte(')')
query = append(query, parentsQuery.String())
@@ -894,6 +923,10 @@ func (f *Fs) list(ctx context.Context, dirIDs []string, title string, directorie
if f.rootFolderID == "appDataFolder" {
list.Spaces("appDataFolder")
}
+ // Add resource Keys if necessary
+ if resourceKeysHeader != "" {
+ list.Header().Add("X-Goog-Drive-Resource-Keys", resourceKeysHeader)
+ }
fields := fmt.Sprintf("files(%s),nextPageToken,incompleteSearch", f.fileFields)
@@ -1153,15 +1186,16 @@ func newFs(ctx context.Context, name, path string, m configmap.Mapper) (*Fs, err
ci := fs.GetConfig(ctx)
f := &Fs{
- name: name,
- root: root,
- opt: *opt,
- ci: ci,
- pacer: fs.NewPacer(ctx, pacer.NewGoogleDrive(pacer.MinSleep(opt.PacerMinSleep), pacer.Burst(opt.PacerBurst))),
- m: m,
- grouping: listRGrouping,
- listRmu: new(sync.Mutex),
- listRempties: make(map[string]struct{}),
+ name: name,
+ root: root,
+ opt: *opt,
+ ci: ci,
+ pacer: fs.NewPacer(ctx, pacer.NewGoogleDrive(pacer.MinSleep(opt.PacerMinSleep), pacer.Burst(opt.PacerBurst))),
+ m: m,
+ grouping: listRGrouping,
+ listRmu: new(sync.Mutex),
+ listRempties: make(map[string]struct{}),
+ dirResourceKeys: new(sync.Map),
}
f.isTeamDrive = opt.TeamDriveID != ""
f.fileFields = f.getFileFields()
@@ -1223,6 +1257,11 @@ func NewFs(ctx context.Context, name, path string, m configmap.Mapper) (fs.Fs, e
f.dirCache = dircache.New(f.root, f.rootFolderID, f)
+ // If resource key is set then cache it for the root folder id
+ if f.opt.ResourceKey != "" {
+ f.dirResourceKeys.Store(f.rootFolderID, f.opt.ResourceKey)
+ }
+
// Parse extensions
if f.opt.Extensions != "" {
if f.opt.ExportExtensions != defaultExportExtensions {
@@ -2091,6 +2130,10 @@ func (f *Fs) itemToDirEntry(ctx context.Context, remote string, item *drive.File
case item.MimeType == driveFolderType:
// cache the directory ID for later lookups
f.dirCache.Put(remote, item.Id)
+ // cache the resource key for later lookups
+ if item.ResourceKey != "" {
+ f.dirResourceKeys.Store(item.Id, item.ResourceKey)
+ }
when, _ := time.Parse(timeFormatIn, item.ModifiedTime)
d := fs.NewDir(remote, when).SetID(item.Id)
if len(item.Parents) > 0 {
From 4d72abf3899becdc6e8183057e326e281694c920 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 5 May 2022 17:12:57 +0100
Subject: [PATCH 053/560] dropbox: fix nil pointer exception on dropbox
impersonate user not found
Fixes #6139
---
backend/dropbox/dropbox.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/backend/dropbox/dropbox.go b/backend/dropbox/dropbox.go
index 000bfd5e42eeb..69d0b4e7d326c 100644
--- a/backend/dropbox/dropbox.go
+++ b/backend/dropbox/dropbox.go
@@ -472,10 +472,12 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
args := team.NewMembersGetInfoArgs(members)
memberIds, err := f.team.MembersGetInfo(args)
-
if err != nil {
return nil, fmt.Errorf("invalid dropbox team member: %q: %w", opt.Impersonate, err)
}
+ if len(memberIds) == 0 || memberIds[0].MemberInfo == nil || memberIds[0].MemberInfo.Profile == nil {
+ return nil, fmt.Errorf("dropbox team member not found: %q", opt.Impersonate)
+ }
cfg.AsMemberID = memberIds[0].MemberInfo.Profile.MemberProfile.TeamMemberId
}
From 4f94b2780035114b8aa963a00c5725c5b84f9a3c Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 26 Apr 2022 09:22:30 +0100
Subject: [PATCH 054/560] check: implement --no-traverse and
--no-unicode-normalization
See: https://forum.rclone.org/t/rclone-check-head-or-list-object-from-source/30400
---
fs/operations/check.go | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/fs/operations/check.go b/fs/operations/check.go
index 12c63e70182d2..a7a52e299b28d 100644
--- a/fs/operations/check.go
+++ b/fs/operations/check.go
@@ -219,11 +219,13 @@ func CheckFn(ctx context.Context, opt *CheckOpt) error {
// set up a march over fdst and fsrc
m := &march.March{
- Ctx: ctx,
- Fdst: c.opt.Fdst,
- Fsrc: c.opt.Fsrc,
- Dir: "",
- Callback: c,
+ Ctx: ctx,
+ Fdst: c.opt.Fdst,
+ Fsrc: c.opt.Fsrc,
+ Dir: "",
+ Callback: c,
+ NoTraverse: ci.NoTraverse,
+ NoUnicodeNormalization: ci.NoUnicodeNormalization,
}
fs.Debugf(c.opt.Fdst, "Waiting for checks to finish")
err := m.Run(ctx)
From 1d2fe0d8564bc679ece166c24b24e6fe7dc1455c Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sun, 3 Apr 2022 13:39:42 +0100
Subject: [PATCH 055/560] union: enable passing of options to upstreams and
policies #6071
This factors out the options into a sub package so they can be passed
to upstreams and used in policies.
---
backend/union/common/options.go | 16 ++++++++++++++++
backend/union/union.go | 17 ++++-------------
backend/union/upstream/upstream.go | 7 +++++--
3 files changed, 25 insertions(+), 15 deletions(-)
create mode 100644 backend/union/common/options.go
diff --git a/backend/union/common/options.go b/backend/union/common/options.go
new file mode 100644
index 0000000000000..cc275cc3722b1
--- /dev/null
+++ b/backend/union/common/options.go
@@ -0,0 +1,16 @@
+// Package common defines code common to the union and the policies
+//
+// These need to be defined in a separate package to avoid import loops
+package common
+
+import "github.com/rclone/rclone/fs"
+
+// Options defines the configuration for this backend
+type Options struct {
+ Upstreams fs.SpaceSepList `config:"upstreams"`
+ Remotes fs.SpaceSepList `config:"remotes"` // Deprecated
+ ActionPolicy string `config:"action_policy"`
+ CreatePolicy string `config:"create_policy"`
+ SearchPolicy string `config:"search_policy"`
+ CacheTime int `config:"cache_time"`
+}
diff --git a/backend/union/union.go b/backend/union/union.go
index ed17cd02099e5..dc4e2c5896e9d 100644
--- a/backend/union/union.go
+++ b/backend/union/union.go
@@ -13,6 +13,7 @@ import (
"sync"
"time"
+ "github.com/rclone/rclone/backend/union/common"
"github.com/rclone/rclone/backend/union/policy"
"github.com/rclone/rclone/backend/union/upstream"
"github.com/rclone/rclone/fs"
@@ -54,21 +55,11 @@ func init() {
fs.Register(fsi)
}
-// Options defines the configuration for this backend
-type Options struct {
- Upstreams fs.SpaceSepList `config:"upstreams"`
- Remotes fs.SpaceSepList `config:"remotes"` // Deprecated
- ActionPolicy string `config:"action_policy"`
- CreatePolicy string `config:"create_policy"`
- SearchPolicy string `config:"search_policy"`
- CacheTime int `config:"cache_time"`
-}
-
// Fs represents a union of upstreams
type Fs struct {
name string // name of this remote
features *fs.Features // optional features
- opt Options // options for this Fs
+ opt common.Options // options for this Fs
root string // the path we are working on
upstreams []*upstream.Fs // slice of upstreams
hashSet hash.Set // intersection of hash types
@@ -808,7 +799,7 @@ func (f *Fs) Shutdown(ctx context.Context) error {
// The returned Fs is the actual Fs, referenced by remote in the config
func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
// Parse config into Options struct
- opt := new(Options)
+ opt := new(common.Options)
err := configstruct.Set(m, opt)
if err != nil {
return nil, err
@@ -836,7 +827,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
errs := Errors(make([]error, len(opt.Upstreams)))
multithread(len(opt.Upstreams), func(i int) {
u := opt.Upstreams[i]
- upstreams[i], errs[i] = upstream.New(ctx, u, root, time.Duration(opt.CacheTime)*time.Second)
+ upstreams[i], errs[i] = upstream.New(ctx, u, root, opt)
})
var usedUpstreams []*upstream.Fs
var fserr error
diff --git a/backend/union/upstream/upstream.go b/backend/union/upstream/upstream.go
index 86575c512c31f..41f30f4009ce5 100644
--- a/backend/union/upstream/upstream.go
+++ b/backend/union/upstream/upstream.go
@@ -11,6 +11,7 @@ import (
"sync/atomic"
"time"
+ "github.com/rclone/rclone/backend/union/common"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/cache"
"github.com/rclone/rclone/fs/fspath"
@@ -26,6 +27,7 @@ type Fs struct {
fs.Fs
RootFs fs.Fs
RootPath string
+ Opt *common.Options
writable bool
creatable bool
usage *fs.Usage // Cache the usage
@@ -61,17 +63,18 @@ type Entry interface {
// New creates a new Fs based on the
// string formatted `type:root_path(:ro/:nc)`
-func New(ctx context.Context, remote, root string, cacheTime time.Duration) (*Fs, error) {
+func New(ctx context.Context, remote, root string, opt *common.Options) (*Fs, error) {
configName, fsPath, err := fspath.SplitFs(remote)
if err != nil {
return nil, err
}
f := &Fs{
RootPath: strings.TrimRight(root, "/"),
+ Opt: opt,
writable: true,
creatable: true,
cacheExpiry: time.Now().Unix(),
- cacheTime: cacheTime,
+ cacheTime: time.Duration(opt.CacheTime) * time.Second,
usage: &fs.Usage{},
}
if strings.HasSuffix(fsPath, ":ro") {
From 1e1af46a12afcd88d92fc22cffd62ca8b4e9396d Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 4 Apr 2022 09:32:50 +0100
Subject: [PATCH 056/560] union: fix get free space for remotes which don't
support it #6071
Before this fix GetFreeSpace returned math.MaxInt64 for remotes which
don't support reading free space, however this is used in various
comparison routines as a too large value, meaning that remotes of size
math.MaxInt64 were never being selected.
This fixes GetFreeSpace to return math.MaxInt64 - 1 so then can be selected.
It also fixes GetUsedSpace the same way however as the default for not
supported was 0 this was very unlikely to have ever caused a problem.
---
backend/union/upstream/upstream.go | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/backend/union/upstream/upstream.go b/backend/union/upstream/upstream.go
index 41f30f4009ce5..1a85d783fe44b 100644
--- a/backend/union/upstream/upstream.go
+++ b/backend/union/upstream/upstream.go
@@ -259,22 +259,30 @@ func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
}
// GetFreeSpace get the free space of the fs
+//
+// This is returned as 0..math.MaxInt64-1 leaving math.MaxInt64 as a sentinel
func (f *Fs) GetFreeSpace() (int64, error) {
if atomic.LoadInt64(&f.cacheExpiry) <= time.Now().Unix() {
err := f.updateUsage()
if err != nil {
- return math.MaxInt64, ErrUsageFieldNotSupported
+ return math.MaxInt64 - 1, ErrUsageFieldNotSupported
}
}
f.cacheMutex.RLock()
defer f.cacheMutex.RUnlock()
if f.usage.Free == nil {
- return math.MaxInt64, ErrUsageFieldNotSupported
+ return math.MaxInt64 - 1, ErrUsageFieldNotSupported
+ }
+ free := *f.usage.Free
+ if free >= math.MaxInt64 {
+ free = math.MaxInt64 - 1
}
- return *f.usage.Free, nil
+ return free, nil
}
// GetUsedSpace get the used space of the fs
+//
+// This is returned as 0..math.MaxInt64-1 leaving math.MaxInt64 as a sentinel
func (f *Fs) GetUsedSpace() (int64, error) {
if atomic.LoadInt64(&f.cacheExpiry) <= time.Now().Unix() {
err := f.updateUsage()
@@ -287,7 +295,11 @@ func (f *Fs) GetUsedSpace() (int64, error) {
if f.usage.Used == nil {
return 0, ErrUsageFieldNotSupported
}
- return *f.usage.Used, nil
+ used := *f.usage.Used
+ if used >= math.MaxInt64 {
+ used = math.MaxInt64 - 1
+ }
+ return used, nil
}
// GetNumObjects get the number of objects of the fs
From 29e37749b3f83600d26b17c0fd62971ad93cb1f8 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 4 Apr 2022 09:51:56 +0100
Subject: [PATCH 057/560] union: fix eplus policy to select correct entry for
existing files #6071
---
backend/union/policy/eplus.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/union/policy/eplus.go b/backend/union/policy/eplus.go
index d1aad2c9e0708..57f97c23190f4 100644
--- a/backend/union/policy/eplus.go
+++ b/backend/union/policy/eplus.go
@@ -42,7 +42,7 @@ func (p *EpLus) lusEntries(entries []upstream.Entry) (upstream.Entry, error) {
var minUsedSpace int64 = math.MaxInt64
var lusEntry upstream.Entry
for _, e := range entries {
- space, err := e.UpstreamFs().GetFreeSpace()
+ space, err := e.UpstreamFs().GetUsedSpace()
if err != nil {
fs.LogPrintf(fs.LogLevelNotice, nil,
"Used Space is not supported for upstream %s, treating as 0", e.UpstreamFs().Name())
From 16514290411b99b5887653043647429e8c6cba91 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 4 Apr 2022 09:55:05 +0100
Subject: [PATCH 058/560] union: add min_free_space option for lfs/eplfs
policies - fixes #6071
---
backend/union/common/options.go | 1 +
backend/union/policy/eplfs.go | 19 +++++++++++++------
backend/union/union.go | 8 ++++++++
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/backend/union/common/options.go b/backend/union/common/options.go
index cc275cc3722b1..edc715203f430 100644
--- a/backend/union/common/options.go
+++ b/backend/union/common/options.go
@@ -13,4 +13,5 @@ type Options struct {
CreatePolicy string `config:"create_policy"`
SearchPolicy string `config:"search_policy"`
CacheTime int `config:"cache_time"`
+ MinFreeSpace fs.SizeSuffix `config:"min_free_space"`
}
diff --git a/backend/union/policy/eplfs.go b/backend/union/policy/eplfs.go
index a2788138c2406..28d84ff5c7bb6 100644
--- a/backend/union/policy/eplfs.go
+++ b/backend/union/policy/eplfs.go
@@ -2,6 +2,7 @@ package policy
import (
"context"
+ "errors"
"math"
"github.com/rclone/rclone/backend/union/upstream"
@@ -18,6 +19,8 @@ type EpLfs struct {
EpAll
}
+var errNoUpstreamsFound = errors.New("no upstreams found with more than min_free_space space spare")
+
func (p *EpLfs) lfs(upstreams []*upstream.Fs) (*upstream.Fs, error) {
var minFreeSpace int64 = math.MaxInt64
var lfsupstream *upstream.Fs
@@ -27,31 +30,35 @@ func (p *EpLfs) lfs(upstreams []*upstream.Fs) (*upstream.Fs, error) {
fs.LogPrintf(fs.LogLevelNotice, nil,
"Free Space is not supported for upstream %s, treating as infinite", u.Name())
}
- if space < minFreeSpace {
+ if space < minFreeSpace && space > int64(u.Opt.MinFreeSpace) {
minFreeSpace = space
lfsupstream = u
}
}
if lfsupstream == nil {
- return nil, fs.ErrorObjectNotFound
+ return nil, errNoUpstreamsFound
}
return lfsupstream, nil
}
func (p *EpLfs) lfsEntries(entries []upstream.Entry) (upstream.Entry, error) {
- var minFreeSpace int64
+ var minFreeSpace int64 = math.MaxInt64
var lfsEntry upstream.Entry
for _, e := range entries {
- space, err := e.UpstreamFs().GetFreeSpace()
+ u := e.UpstreamFs()
+ space, err := u.GetFreeSpace()
if err != nil {
fs.LogPrintf(fs.LogLevelNotice, nil,
- "Free Space is not supported for upstream %s, treating as infinite", e.UpstreamFs().Name())
+ "Free Space is not supported for upstream %s, treating as infinite", u.Name())
}
- if space < minFreeSpace {
+ if space < minFreeSpace && space > int64(u.Opt.MinFreeSpace) {
minFreeSpace = space
lfsEntry = e
}
}
+ if lfsEntry == nil {
+ return nil, errNoUpstreamsFound
+ }
return lfsEntry, nil
}
diff --git a/backend/union/union.go b/backend/union/union.go
index dc4e2c5896e9d..9776c55d2a54f 100644
--- a/backend/union/union.go
+++ b/backend/union/union.go
@@ -50,6 +50,14 @@ func init() {
Name: "cache_time",
Help: "Cache time of usage and free space (in seconds).\n\nThis option is only useful when a path preserving policy is used.",
Default: 120,
+ }, {
+ Name: "min_free_space",
+ Help: `Minimum viable free space for lfs/eplfs policies.
+
+If a remote has less than this much free space then it won't be
+considered for use in lfs or eplfs policies.`,
+ Advanced: true,
+ Default: fs.Gibi,
}},
}
fs.Register(fsi)
From 95e093475596a1764931908abb0426bb3ea0fe7c Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 28 Mar 2022 12:47:22 +0100
Subject: [PATCH 059/560] sftp: add --sftp-chunk-size to control packets sizes
for high latency links
See: https://forum.rclone.org/t/increasing-sftp-transfer-speed/29928
---
backend/sftp/sftp.go | 72 +++++++++++++++++++++++++++++---------------
1 file changed, 47 insertions(+), 25 deletions(-)
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index 305fbde983925..ae617069d38ec 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -272,6 +272,26 @@ given, rclone will empty the connection pool.
Set to 0 to keep connections indefinitely.
`,
Advanced: true,
+ }, {
+ Name: "chunk_size",
+ Help: `Upload and download chunk size.
+
+This controls the maximum packet size used in the SFTP protocol. The
+RFC limits this to 32768 bytes (32k), however a lot of servers
+support larger sizes and setting it larger will increase transfer
+speed dramatically on high latency links.
+
+Only use a setting higher than 32k if you always connect to the same
+server or after sufficiently broad testing.
+
+For example using the value of 252k with OpenSSH works well with its
+maximum packet size of 256k.
+
+If you get the error "failed to send packet header: EOF" when copying
+a large file, try lowering this number.
+`,
+ Default: 32 * fs.Kibi,
+ Advanced: true,
}},
}
fs.Register(fsi)
@@ -279,31 +299,32 @@ Set to 0 to keep connections indefinitely.
// Options defines the configuration for this backend
type Options struct {
- Host string `config:"host"`
- User string `config:"user"`
- Port string `config:"port"`
- Pass string `config:"pass"`
- KeyPem string `config:"key_pem"`
- KeyFile string `config:"key_file"`
- KeyFilePass string `config:"key_file_pass"`
- PubKeyFile string `config:"pubkey_file"`
- KnownHostsFile string `config:"known_hosts_file"`
- KeyUseAgent bool `config:"key_use_agent"`
- UseInsecureCipher bool `config:"use_insecure_cipher"`
- DisableHashCheck bool `config:"disable_hashcheck"`
- AskPassword bool `config:"ask_password"`
- PathOverride string `config:"path_override"`
- SetModTime bool `config:"set_modtime"`
- ShellType string `config:"shell_type"`
- Md5sumCommand string `config:"md5sum_command"`
- Sha1sumCommand string `config:"sha1sum_command"`
- SkipLinks bool `config:"skip_links"`
- Subsystem string `config:"subsystem"`
- ServerCommand string `config:"server_command"`
- UseFstat bool `config:"use_fstat"`
- DisableConcurrentReads bool `config:"disable_concurrent_reads"`
- DisableConcurrentWrites bool `config:"disable_concurrent_writes"`
- IdleTimeout fs.Duration `config:"idle_timeout"`
+ Host string `config:"host"`
+ User string `config:"user"`
+ Port string `config:"port"`
+ Pass string `config:"pass"`
+ KeyPem string `config:"key_pem"`
+ KeyFile string `config:"key_file"`
+ KeyFilePass string `config:"key_file_pass"`
+ PubKeyFile string `config:"pubkey_file"`
+ KnownHostsFile string `config:"known_hosts_file"`
+ KeyUseAgent bool `config:"key_use_agent"`
+ UseInsecureCipher bool `config:"use_insecure_cipher"`
+ DisableHashCheck bool `config:"disable_hashcheck"`
+ AskPassword bool `config:"ask_password"`
+ PathOverride string `config:"path_override"`
+ SetModTime bool `config:"set_modtime"`
+ ShellType string `config:"shell_type"`
+ Md5sumCommand string `config:"md5sum_command"`
+ Sha1sumCommand string `config:"sha1sum_command"`
+ SkipLinks bool `config:"skip_links"`
+ Subsystem string `config:"subsystem"`
+ ServerCommand string `config:"server_command"`
+ UseFstat bool `config:"use_fstat"`
+ DisableConcurrentReads bool `config:"disable_concurrent_reads"`
+ DisableConcurrentWrites bool `config:"disable_concurrent_writes"`
+ IdleTimeout fs.Duration `config:"idle_timeout"`
+ ChunkSize fs.SizeSuffix `config:"chunk_size"`
}
// Fs stores the interface to the remote SFTP files
@@ -481,6 +502,7 @@ func (f *Fs) newSftpClient(conn *ssh.Client, opts ...sftp.ClientOption) (*sftp.C
sftp.UseFstat(f.opt.UseFstat),
sftp.UseConcurrentReads(!f.opt.DisableConcurrentReads),
sftp.UseConcurrentWrites(!f.opt.DisableConcurrentWrites),
+ sftp.MaxPacketUnchecked(int(f.opt.ChunkSize)),
)
return sftp.NewClientPipe(pr, pw, opts...)
}
From 78120d40d9bd5171d58aa4839c4766a4eaef4461 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 28 Mar 2022 12:57:34 +0100
Subject: [PATCH 060/560] sftp: add --sftp-concurrency to improve high latency
transfers
See: https://forum.rclone.org/t/increasing-sftp-transfer-speed/29928
---
backend/sftp/sftp.go | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index ae617069d38ec..a228dece18182 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -292,6 +292,16 @@ a large file, try lowering this number.
`,
Default: 32 * fs.Kibi,
Advanced: true,
+ }, {
+ Name: "concurrency",
+ Help: `The maximum number of outstanding requests for one file
+
+This controls the maximum number of outstanding requests for one file.
+Increasing it will increase throughput on high latency links at the
+cost of using more memory.
+`,
+ Default: 64,
+ Advanced: true,
}},
}
fs.Register(fsi)
@@ -325,6 +335,7 @@ type Options struct {
DisableConcurrentWrites bool `config:"disable_concurrent_writes"`
IdleTimeout fs.Duration `config:"idle_timeout"`
ChunkSize fs.SizeSuffix `config:"chunk_size"`
+ Concurrency int `config:"concurrency"`
}
// Fs stores the interface to the remote SFTP files
@@ -503,6 +514,7 @@ func (f *Fs) newSftpClient(conn *ssh.Client, opts ...sftp.ClientOption) (*sftp.C
sftp.UseConcurrentReads(!f.opt.DisableConcurrentReads),
sftp.UseConcurrentWrites(!f.opt.DisableConcurrentWrites),
sftp.MaxPacketUnchecked(int(f.opt.ChunkSize)),
+ sftp.MaxConcurrentRequestsPerFile(f.opt.Concurrency),
)
return sftp.NewClientPipe(pr, pw, opts...)
}
From 60d87185e154109d178e2b92d185877f305253a1 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 11 Apr 2022 10:41:17 +0100
Subject: [PATCH 061/560] sftp: add --sftp-set-env option to set environment
variables
Fixes #6094
---
backend/sftp/sftp.go | 100 +++++++++++++++++++++++++++++++------------
1 file changed, 73 insertions(+), 27 deletions(-)
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index a228dece18182..5e9cbe18dfd95 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -302,6 +302,27 @@ cost of using more memory.
`,
Default: 64,
Advanced: true,
+ }, {
+ Name: "set_env",
+ Default: fs.SpaceSepList{},
+ Help: `Environment variables to pass to sftp and commands
+
+Set environment variables in the form:
+
+ VAR=value
+
+to be passed to the sftp client and to any commands run (eg md5sum).
+
+Pass multiple variables space separated, eg
+
+ VAR1=value VAR2=value
+
+and pass variables with spaces in in quotes, eg
+
+ "VAR3=value with space" "VAR4=value with space" VAR5=nospacehere
+
+`,
+ Advanced: true,
}},
}
fs.Register(fsi)
@@ -309,33 +330,34 @@ cost of using more memory.
// Options defines the configuration for this backend
type Options struct {
- Host string `config:"host"`
- User string `config:"user"`
- Port string `config:"port"`
- Pass string `config:"pass"`
- KeyPem string `config:"key_pem"`
- KeyFile string `config:"key_file"`
- KeyFilePass string `config:"key_file_pass"`
- PubKeyFile string `config:"pubkey_file"`
- KnownHostsFile string `config:"known_hosts_file"`
- KeyUseAgent bool `config:"key_use_agent"`
- UseInsecureCipher bool `config:"use_insecure_cipher"`
- DisableHashCheck bool `config:"disable_hashcheck"`
- AskPassword bool `config:"ask_password"`
- PathOverride string `config:"path_override"`
- SetModTime bool `config:"set_modtime"`
- ShellType string `config:"shell_type"`
- Md5sumCommand string `config:"md5sum_command"`
- Sha1sumCommand string `config:"sha1sum_command"`
- SkipLinks bool `config:"skip_links"`
- Subsystem string `config:"subsystem"`
- ServerCommand string `config:"server_command"`
- UseFstat bool `config:"use_fstat"`
- DisableConcurrentReads bool `config:"disable_concurrent_reads"`
- DisableConcurrentWrites bool `config:"disable_concurrent_writes"`
- IdleTimeout fs.Duration `config:"idle_timeout"`
- ChunkSize fs.SizeSuffix `config:"chunk_size"`
- Concurrency int `config:"concurrency"`
+ Host string `config:"host"`
+ User string `config:"user"`
+ Port string `config:"port"`
+ Pass string `config:"pass"`
+ KeyPem string `config:"key_pem"`
+ KeyFile string `config:"key_file"`
+ KeyFilePass string `config:"key_file_pass"`
+ PubKeyFile string `config:"pubkey_file"`
+ KnownHostsFile string `config:"known_hosts_file"`
+ KeyUseAgent bool `config:"key_use_agent"`
+ UseInsecureCipher bool `config:"use_insecure_cipher"`
+ DisableHashCheck bool `config:"disable_hashcheck"`
+ AskPassword bool `config:"ask_password"`
+ PathOverride string `config:"path_override"`
+ SetModTime bool `config:"set_modtime"`
+ ShellType string `config:"shell_type"`
+ Md5sumCommand string `config:"md5sum_command"`
+ Sha1sumCommand string `config:"sha1sum_command"`
+ SkipLinks bool `config:"skip_links"`
+ Subsystem string `config:"subsystem"`
+ ServerCommand string `config:"server_command"`
+ UseFstat bool `config:"use_fstat"`
+ DisableConcurrentReads bool `config:"disable_concurrent_reads"`
+ DisableConcurrentWrites bool `config:"disable_concurrent_writes"`
+ IdleTimeout fs.Duration `config:"idle_timeout"`
+ ChunkSize fs.SizeSuffix `config:"chunk_size"`
+ Concurrency int `config:"concurrency"`
+ SetEnv fs.SpaceSepList `config:"set_env"`
}
// Fs stores the interface to the remote SFTP files
@@ -483,6 +505,22 @@ func (f *Fs) sftpConnection(ctx context.Context) (c *conn, err error) {
return c, nil
}
+// Set any environment variables on the ssh.Session
+func (f *Fs) setEnv(s *ssh.Session) error {
+ for _, env := range f.opt.SetEnv {
+ equal := strings.IndexRune(env, '=')
+ if equal < 0 {
+ return fmt.Errorf("no = found in env var %q", env)
+ }
+ // fs.Debugf(f, "Setting env %q = %q", env[:equal], env[equal+1:])
+ err := s.Setenv(env[:equal], env[equal+1:])
+ if err != nil {
+ return fmt.Errorf("Failed to set env var %q: %w", env[:equal], err)
+ }
+ }
+ return nil
+}
+
// Creates a new SFTP client on conn, using the specified subsystem
// or sftp server, and zero or more option functions
func (f *Fs) newSftpClient(conn *ssh.Client, opts ...sftp.ClientOption) (*sftp.Client, error) {
@@ -490,6 +528,10 @@ func (f *Fs) newSftpClient(conn *ssh.Client, opts ...sftp.ClientOption) (*sftp.C
if err != nil {
return nil, err
}
+ err = f.setEnv(s)
+ if err != nil {
+ return nil, err
+ }
pw, err := s.StdinPipe()
if err != nil {
return nil, err
@@ -1243,6 +1285,10 @@ func (f *Fs) run(ctx context.Context, cmd string) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("run: get SFTP session: %w", err)
}
+ err = f.setEnv(session)
+ if err != nil {
+ return nil, err
+ }
defer func() {
_ = session.Close()
}()
From 3f6186917992a61793a29240705b43562d5a31fb Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Fri, 15 Apr 2022 12:11:14 +0100
Subject: [PATCH 062/560] cmount: add tracing for *xattr FUSE callbacks
See: https://github.com/winfsp/cgofuse/issues/66
See: https://forum.rclone.org/t/cannot-copy-files-to-mounted-azure-storage-windows/30092
---
cmd/cmount/fs.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/cmd/cmount/fs.go b/cmd/cmount/fs.go
index 553712b1636b2..c097611b754fd 100644
--- a/cmd/cmount/fs.go
+++ b/cmd/cmount/fs.go
@@ -545,21 +545,25 @@ func (fsys *FS) Fsyncdir(path string, datasync bool, fh uint64) (errc int) {
// Setxattr sets extended attributes.
func (fsys *FS) Setxattr(path string, name string, value []byte, flags int) (errc int) {
+ defer log.Trace(path, "name=%q, value=%q, flags=%d", name, value, flags)("errc=%d", &errc)
return -fuse.ENOSYS
}
// Getxattr gets extended attributes.
func (fsys *FS) Getxattr(path string, name string) (errc int, value []byte) {
+ defer log.Trace(path, "name=%q", name)("errc=%d, value=%q", &errc, &value)
return -fuse.ENOSYS, nil
}
// Removexattr removes extended attributes.
func (fsys *FS) Removexattr(path string, name string) (errc int) {
+ defer log.Trace(path, "name=%q", name)("errc=%d", &errc)
return -fuse.ENOSYS
}
// Listxattr lists extended attributes.
func (fsys *FS) Listxattr(path string, fill func(name string) bool) (errc int) {
+ defer log.Trace(path, "fill=%p", fill)("errc=%d", &errc)
return -fuse.ENOSYS
}
From 4ac875a811ad10589186b5a199e8b1bec33ff226 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 29 Nov 2021 16:19:00 +0000
Subject: [PATCH 063/560] sync: fix --max-duration and --cutoff-mode soft
Before this change using --max-duration and --cutoff-mode soft would
work like --cutoff-mode hard.
This bug was introduced in this commit which made transfers be
cancelable - before that transfers couldn't be canceled.
122a47fba655704b accounting: Allow transfers to be canceled with context #3257
This change adds the timeout to the input context for reading files
rather than the transfer context so the files transfers themselves
aren't canceled if --cutoff-mode soft is in action.
This also adds a test for cutoff mode soft and max duration which was
missing.
See: https://forum.rclone.org/t/max-duration-and-retries-not-working-correctly/27738
---
fs/sync/sync.go | 27 +++++++++++++++++++------
fs/sync/sync_test.go | 47 ++++++++++++++++++++++++++++++--------------
2 files changed, 53 insertions(+), 21 deletions(-)
diff --git a/fs/sync/sync.go b/fs/sync/sync.go
index f9844ea165b66..58e74e22a6377 100644
--- a/fs/sync/sync.go
+++ b/fs/sync/sync.go
@@ -145,16 +145,29 @@ func newSyncCopyMove(ctx context.Context, fdst, fsrc fs.Fs, deleteMode fs.Delete
if err != nil {
return nil, err
}
- // If a max session duration has been defined add a deadline to the context
if ci.MaxDuration > 0 {
s.maxDurationEndTime = time.Now().Add(ci.MaxDuration)
- fs.Infof(s.fdst, "Transfer session deadline: %s", s.maxDurationEndTime.Format("2006/01/02 15:04:05"))
+ fs.Infof(s.fdst, "Transfer session %v deadline: %s", ci.CutoffMode, s.maxDurationEndTime.Format("2006/01/02 15:04:05"))
+ }
+ // If a max session duration has been defined add a deadline
+ // to the main context if cutoff mode is hard. This will cut
+ // the transfers off.
+ if !s.maxDurationEndTime.IsZero() && ci.CutoffMode == fs.CutoffModeHard {
s.ctx, s.cancel = context.WithDeadline(ctx, s.maxDurationEndTime)
} else {
s.ctx, s.cancel = context.WithCancel(ctx)
}
- // Input context - cancel this for graceful stop
- s.inCtx, s.inCancel = context.WithCancel(s.ctx)
+ // Input context - cancel this for graceful stop.
+ //
+ // If a max session duration has been defined add a deadline
+ // to the input context if cutoff mode is graceful or soft.
+ // This won't stop the transfers but will cut the
+ // list/check/transfer pipelines.
+ if !s.maxDurationEndTime.IsZero() && ci.CutoffMode != fs.CutoffModeHard {
+ s.inCtx, s.inCancel = context.WithDeadline(s.ctx, s.maxDurationEndTime)
+ } else {
+ s.inCtx, s.inCancel = context.WithCancel(s.ctx)
+ }
if s.noTraverse && s.deleteMode != fs.DeleteModeOff {
if !fi.HaveFilesFrom() {
fs.Errorf(nil, "Ignoring --no-traverse with sync")
@@ -904,8 +917,9 @@ func (s *syncCopyMove) run() error {
s.processError(s.deleteEmptyDirectories(s.ctx, s.fsrc, s.srcEmptyDirs))
}
- // Read the error out of the context if there is one
+ // Read the error out of the contexts if there is one
s.processError(s.ctx.Err())
+ s.processError(s.inCtx.Err())
// If the duration was exceeded then add a Fatal Error so we don't retry
if !s.maxDurationEndTime.IsZero() && time.Since(s.maxDurationEndTime) > 0 {
@@ -918,7 +932,8 @@ func (s *syncCopyMove) run() error {
fs.Infof(nil, "There was nothing to transfer")
}
- // cancel the context to free resources
+ // cancel the contexts to free resources
+ s.inCancel()
s.cancel()
return s.currentError()
}
diff --git a/fs/sync/sync_test.go b/fs/sync/sync_test.go
index 4bf60c47121da..72e6a65e781f3 100644
--- a/fs/sync/sync_test.go
+++ b/fs/sync/sync_test.go
@@ -1002,7 +1002,7 @@ func TestSyncWithUpdateOlder(t *testing.T) {
}
// Test with a max transfer duration
-func TestSyncWithMaxDuration(t *testing.T) {
+func testSyncWithMaxDuration(t *testing.T, cutoffMode fs.CutoffMode) {
ctx := context.Background()
ctx, ci := fs.AddConfig(ctx)
if *fstest.RemoteName != "" {
@@ -1013,32 +1013,49 @@ func TestSyncWithMaxDuration(t *testing.T) {
maxDuration := 250 * time.Millisecond
ci.MaxDuration = maxDuration
- bytesPerSecond := 300
- accounting.TokenBucket.SetBwLimit(fs.BwPair{Tx: fs.SizeSuffix(bytesPerSecond), Rx: fs.SizeSuffix(bytesPerSecond)})
+ ci.CutoffMode = cutoffMode
+ ci.CheckFirst = true
+ ci.OrderBy = "size"
ci.Transfers = 1
+ ci.Checkers = 1
+ bytesPerSecond := 10 * 1024
+ accounting.TokenBucket.SetBwLimit(fs.BwPair{Tx: fs.SizeSuffix(bytesPerSecond), Rx: fs.SizeSuffix(bytesPerSecond)})
defer accounting.TokenBucket.SetBwLimit(fs.BwPair{Tx: -1, Rx: -1})
- // 5 files of 60 bytes at 60 Byte/s 5 seconds
- testFiles := make([]fstest.Item, 5)
- for i := 0; i < len(testFiles); i++ {
- testFiles[i] = r.WriteFile(fmt.Sprintf("file%d", i), "------------------------------------------------------------", t1)
- }
-
- fstest.CheckListing(t, r.Flocal, testFiles)
+ // write one small file which we expect to transfer and one big one which we don't
+ file1 := r.WriteFile("file1", string(make([]byte, 16)), t1)
+ file2 := r.WriteFile("file2", string(make([]byte, 50*1024)), t1)
+ r.CheckLocalItems(t, file1, file2)
+ r.CheckRemoteItems(t)
accounting.GlobalStats().ResetCounters()
startTime := time.Now()
err := Sync(ctx, r.Fremote, r.Flocal, false)
require.True(t, errors.Is(err, errorMaxDurationReached))
+ if cutoffMode == fs.CutoffModeHard {
+ r.CheckRemoteItems(t, file1)
+ assert.Equal(t, int64(1), accounting.GlobalStats().GetTransfers())
+ } else {
+ r.CheckRemoteItems(t, file1, file2)
+ assert.Equal(t, int64(2), accounting.GlobalStats().GetTransfers())
+ }
+
elapsed := time.Since(startTime)
- maxTransferTime := (time.Duration(len(testFiles)) * 60 * time.Second) / time.Duration(bytesPerSecond)
+ const maxTransferTime = 20 * time.Second
what := fmt.Sprintf("expecting elapsed time %v between %v and %v", elapsed, maxDuration, maxTransferTime)
- require.True(t, elapsed >= maxDuration, what)
- require.True(t, elapsed < 5*time.Second, what)
- // we must not have transferred all files during the session
- require.True(t, accounting.GlobalStats().GetTransfers() < int64(len(testFiles)))
+ assert.True(t, elapsed >= maxDuration, what)
+ assert.True(t, elapsed < maxTransferTime, what)
+}
+
+func TestSyncWithMaxDuration(t *testing.T) {
+ t.Run("Hard", func(t *testing.T) {
+ testSyncWithMaxDuration(t, fs.CutoffModeHard)
+ })
+ t.Run("Soft", func(t *testing.T) {
+ testSyncWithMaxDuration(t, fs.CutoffModeSoft)
+ })
}
// Test with TrackRenames set
From fa48b880c2dc27ce5c3bc38b5b84bac95e2b271e Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 24 Nov 2021 12:48:57 +0000
Subject: [PATCH 064/560] s3: retry RequestTimeout errors
See: https://forum.rclone.org/t/s3-failed-upload-large-files-bad-request-400/27695
---
backend/s3/s3.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index ac5e112cb2c66..dd894db18156a 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -2121,6 +2121,10 @@ func (f *Fs) shouldRetry(ctx context.Context, err error) (bool, error) {
if fserrors.ShouldRetry(awsError.OrigErr()) {
return true, err
}
+ // If it is a timeout then we want to retry that
+ if awsError.Code() == "RequestTimeout" {
+ return true, err
+ }
// Failing that, if it's a RequestFailure it's probably got an http status code we can check
if reqErr, ok := err.(awserr.RequestFailure); ok {
// 301 if wrong region for bucket - can only update if running from a bucket
From e7483b40b34f7c01ad08a69c83b56d9fc79edc01 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 30 May 2022 10:14:37 +0100
Subject: [PATCH 065/560] fshttp: add --disable-http-keep-alives to disable
HTTP Keep Alives
See: https://forum.rclone.org/t/getting-rate-limited-before-advertised-limit-on-s3-compatible-object-storage/31010/
---
fs/config.go | 1 +
fs/config/configflags/configflags.go | 1 +
fs/fshttp/http.go | 1 +
3 files changed, 3 insertions(+)
diff --git a/fs/config.go b/fs/config.go
index 5eb85ec616fe6..e96be37d0ec9a 100644
--- a/fs/config.go
+++ b/fs/config.go
@@ -132,6 +132,7 @@ type ConfigInfo struct {
DisableHTTP2 bool
HumanReadable bool
KvLockTime time.Duration // maximum time to keep key-value database locked by process
+ DisableHTTPKeepAlives bool
}
// NewConfig creates a new config with everything set to the default
diff --git a/fs/config/configflags/configflags.go b/fs/config/configflags/configflags.go
index 7c2c8ade96ccd..8e3e8d5584e6f 100644
--- a/fs/config/configflags/configflags.go
+++ b/fs/config/configflags/configflags.go
@@ -137,6 +137,7 @@ func AddFlags(ci *fs.ConfigInfo, flagSet *pflag.FlagSet) {
flags.BoolVarP(flagSet, &ci.DisableHTTP2, "disable-http2", "", ci.DisableHTTP2, "Disable HTTP/2 in the global transport")
flags.BoolVarP(flagSet, &ci.HumanReadable, "human-readable", "", ci.HumanReadable, "Print numbers in a human-readable format, sizes with suffix Ki|Mi|Gi|Ti|Pi")
flags.DurationVarP(flagSet, &ci.KvLockTime, "kv-lock-time", "", ci.KvLockTime, "Maximum time to keep key-value database locked by process")
+ flags.BoolVarP(flagSet, &ci.DisableHTTPKeepAlives, "disable-http-keep-alives", "", ci.DisableHTTPKeepAlives, "Disable HTTP keep-alives and use each connection once.")
}
// ParseHeaders converts the strings passed in via the header flags into HTTPOptions
diff --git a/fs/fshttp/http.go b/fs/fshttp/http.go
index ef7cf285104a0..9384d2fa4a9d1 100644
--- a/fs/fshttp/http.go
+++ b/fs/fshttp/http.go
@@ -53,6 +53,7 @@ func NewTransportCustom(ctx context.Context, customize func(*http.Transport)) ht
t.MaxIdleConns = 2 * t.MaxIdleConnsPerHost
t.TLSHandshakeTimeout = ci.ConnectTimeout
t.ResponseHeaderTimeout = ci.Timeout
+ t.DisableKeepAlives = ci.DisableHTTPKeepAlives
// TLS Config
t.TLSClientConfig = &tls.Config{
From 5e4caa69cefbbaa7e0f8549efc78e44728374289 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 9 Nov 2021 09:45:10 +0000
Subject: [PATCH 066/560] local: make Hash function cancelable via context
---
backend/local/local.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/local/local.go b/backend/local/local.go
index 3c00d53892d9c..e36c56e2c68c8 100644
--- a/backend/local/local.go
+++ b/backend/local/local.go
@@ -903,7 +903,7 @@ func (o *Object) Hash(ctx context.Context, r hash.Type) (string, error) {
return "", fmt.Errorf("hash: failed to open: %w", err)
}
var hashes map[hash.Type]string
- hashes, err = hash.StreamTypes(in, hash.NewHashSet(r))
+ hashes, err = hash.StreamTypes(readers.NewContextReader(ctx, in), hash.NewHashSet(r))
closeErr := in.Close()
if err != nil {
return "", fmt.Errorf("hash: failed to read: %w", err)
From 115f1c2cc9f95ccf0d0b28a058f71f62424f2d0f Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 9 Nov 2021 09:45:36 +0000
Subject: [PATCH 067/560] operations: speed up hash checking by aborting the
other hash if first returns nothing
This speeds up hash checks when a Hash() function returns "" - this
means that the hash can be canceled for the other side.
In the common case of local hash vs remote hash empty this saves a lot
of time.
See: https://forum.rclone.org/t/rclone-s3-backend-copy-is-2x-slower-than-aws-s3-cp/27321/9
---
fs/operations/operations.go | 46 ++++++++++++++++++++++++-------------
1 file changed, 30 insertions(+), 16 deletions(-)
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index c34cdf97d3f60..c21675443eb5a 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -62,36 +62,50 @@ func CheckHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object) (equal b
return equal, ht, err
}
+var errNoHash = errors.New("no hash available")
+
// checkHashes does the work of CheckHashes but takes a hash.Type and
// returns the effective hash type used.
func checkHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object, ht hash.Type) (equal bool, htOut hash.Type, srcHash, dstHash string, err error) {
// Calculate hashes in parallel
g, ctx := errgroup.WithContext(ctx)
+ var srcErr, dstErr error
g.Go(func() (err error) {
- srcHash, err = src.Hash(ctx, ht)
- if err != nil {
- err = fs.CountError(err)
- fs.Errorf(src, "Failed to calculate src hash: %v", err)
+ srcHash, srcErr = src.Hash(ctx, ht)
+ if srcErr != nil {
+ return srcErr
}
- return err
+ if srcHash == "" {
+ fs.Debugf(src, "Src hash empty - aborting Dst hash check")
+ return errNoHash
+ }
+ return nil
})
g.Go(func() (err error) {
- dstHash, err = dst.Hash(ctx, ht)
- if err != nil {
- err = fs.CountError(err)
- fs.Errorf(dst, "Failed to calculate dst hash: %v", err)
+ dstHash, dstErr = dst.Hash(ctx, ht)
+ if dstErr != nil {
+ return dstErr
}
- return err
+ if dstHash == "" {
+ fs.Debugf(src, "Dst hash empty - aborting Src hash check")
+ return errNoHash
+ }
+ return nil
})
err = g.Wait()
- if err != nil {
- return false, ht, srcHash, dstHash, err
- }
- if srcHash == "" {
+ if err == errNoHash {
return true, hash.None, srcHash, dstHash, nil
}
- if dstHash == "" {
- return true, hash.None, srcHash, dstHash, nil
+ if srcErr != nil {
+ err = fs.CountError(srcErr)
+ fs.Errorf(dst, "Failed to calculate src hash: %v", err)
+ }
+ if dstErr != nil {
+ err = fs.CountError(dstErr)
+ fs.Errorf(src, "Failed to calculate dst hash: %v", err)
+ }
+ if err != nil {
+ return false, ht, srcHash, dstHash, err
}
if srcHash != dstHash {
fs.Debugf(src, "%v = %s (%v)", ht, srcHash, src.Fs())
From e57fe14b6105c50b805f41c52b05298da2e59c73 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 8 Jun 2022 09:08:12 +0100
Subject: [PATCH 068/560] mount: log IO errors at ERROR level - fixes #6217
---
cmd/mount/fs.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/cmd/mount/fs.go b/cmd/mount/fs.go
index 43d37802765c3..c89110ecc7f73 100644
--- a/cmd/mount/fs.go
+++ b/cmd/mount/fs.go
@@ -102,5 +102,6 @@ func translateError(err error) error {
case vfs.EINVAL:
return fuse.Errno(syscall.EINVAL)
}
+ fs.Errorf(nil, "IO error: %v", err)
return err
}
From a6ca4b38173320973e6831eb925e22ee19428669 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sun, 7 Nov 2021 12:35:11 +0000
Subject: [PATCH 069/560] test info: check file name lengths using 1,2,3,4 byte
unicode characters
---
cmd/test/info/info.go | 44 +++++++++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 10 deletions(-)
diff --git a/cmd/test/info/info.go b/cmd/test/info/info.go
index 06cdd74e984ca..c4eef5f046d2f 100644
--- a/cmd/test/info/info.go
+++ b/cmd/test/info/info.go
@@ -91,7 +91,7 @@ type results struct {
mu sync.Mutex
stringNeedsEscaping map[string]internal.Position
controlResults map[string]internal.ControlResult
- maxFileLength int
+ maxFileLength [4]int
canWriteUnnormalized bool
canReadUnnormalized bool
canReadRenormalized bool
@@ -125,7 +125,9 @@ func (r *results) Print() {
fmt.Printf("}\n")
}
if checkLength {
- fmt.Printf("maxFileLength = %d\n", r.maxFileLength)
+ for i := range r.maxFileLength {
+ fmt.Printf("maxFileLength = %d // for %d byte unicode characters\n", r.maxFileLength[i], i+1)
+ }
}
if checkNormalization {
fmt.Printf("canWriteUnnormalized = %v\n", r.canWriteUnnormalized)
@@ -150,7 +152,7 @@ func (r *results) WriteJSON() {
report.ControlCharacters = &r.controlResults
}
if checkLength {
- report.MaxFileLength = &r.maxFileLength
+ report.MaxFileLength = &r.maxFileLength[0]
}
if checkNormalization {
report.CanWriteUnnormalized = &r.canWriteUnnormalized
@@ -366,11 +368,27 @@ func (r *results) checkControlsList() {
}
// find the max file name size we can use
-func (r *results) findMaxLength() {
+func (r *results) findMaxLength(characterLength int) {
+ var character rune
+ switch characterLength {
+ case 1:
+ character = 'a'
+ case 2:
+ character = 'á'
+ case 3:
+ character = '世'
+ case 4:
+ character = '🙂'
+ default:
+ panic("Bad characterLength")
+ }
+ if characterLength != len(string(character)) {
+ panic(fmt.Sprintf("Chose the wrong character length %q is %d not %d", character, len(string(character)), characterLength))
+ }
const maxLen = 16 * 1024
- name := make([]byte, maxLen)
+ name := make([]rune, maxLen)
for i := range name {
- name[i] = 'a'
+ name[i] = character
}
// Find the first size of filename we can't write
i := sort.Search(len(name), func(i int) (fail bool) {
@@ -382,16 +400,20 @@ func (r *results) findMaxLength() {
}()
path := string(name[:i])
- _, err := r.writeFile(path)
+ o, err := r.writeFile(path)
if err != nil {
fs.Infof(r.f, "Couldn't write file with name length %d: %v", i, err)
return true
}
fs.Infof(r.f, "Wrote file with name length %d", i)
+ err = o.Remove(context.Background())
+ if err != nil {
+ fs.Errorf(o, "Failed to remove test file")
+ }
return false
})
- r.maxFileLength = i - 1
- fs.Infof(r.f, "Max file length is %d", r.maxFileLength)
+ r.maxFileLength[characterLength-1] = i - 1
+ fs.Infof(r.f, "Max file length is %d when writing %d byte characters %q", r.maxFileLength[characterLength-1], characterLength, character)
}
func (r *results) checkStreaming() {
@@ -447,7 +469,9 @@ func readInfo(ctx context.Context, f fs.Fs) error {
r.checkControls()
}
if checkLength {
- r.findMaxLength()
+ for i := range r.maxFileLength {
+ r.findMaxLength(i + 1)
+ }
}
if checkNormalization {
r.checkUTF8Normalization()
From 7d3648dc46f91bf444c5875f03cadae51cff8aa9 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Fri, 12 Nov 2021 12:48:14 +0000
Subject: [PATCH 070/560] serve ftp: check --passive-port arguments are correct
See: https://forum.rclone.org/t/serve-ftp-passive-port-validity-check/27458
---
cmd/serve/ftp/ftp.go | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/cmd/serve/ftp/ftp.go b/cmd/serve/ftp/ftp.go
index a2a4df159b337..b8022599e02a9 100644
--- a/cmd/serve/ftp/ftp.go
+++ b/cmd/serve/ftp/ftp.go
@@ -13,6 +13,7 @@ import (
"net"
"os"
"os/user"
+ "regexp"
"strconv"
"sync"
"time"
@@ -128,6 +129,8 @@ type server struct {
useTLS bool
}
+var passivePortsRe = regexp.MustCompile(`^\s*\d+\s*-\s*\d+\s*$`)
+
// Make a new FTP to serve the remote
func newServer(ctx context.Context, f fs.Fs, opt *Options) (*server, error) {
host, port, err := net.SplitHostPort(opt.ListenAddr)
@@ -151,6 +154,11 @@ func newServer(ctx context.Context, f fs.Fs, opt *Options) (*server, error) {
}
s.useTLS = s.opt.TLSKey != ""
+ // Check PassivePorts format since the the server library doesn't!
+ if !passivePortsRe.MatchString(opt.PassivePorts) {
+ return nil, fmt.Errorf("invalid format for passive ports %q", opt.PassivePorts)
+ }
+
ftpopt := &ftp.ServerOpts{
Name: "Rclone FTP Server",
WelcomeMessage: "Welcome to Rclone " + fs.Version + " FTP Server",
From 5697dbc80f58b1a97a66e717bfd0fcbffc60ec42 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 18:08:12 +0100
Subject: [PATCH 071/560] local: fix parsing of --local-nounc flag
---
backend/local/local.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/backend/local/local.go b/backend/local/local.go
index e36c56e2c68c8..1a8908720c4f0 100644
--- a/backend/local/local.go
+++ b/backend/local/local.go
@@ -45,6 +45,7 @@ func init() {
Options: []fs.Option{{
Name: "nounc",
Help: "Disable UNC (long path names) conversion on Windows.",
+ Default: false,
Advanced: runtime.GOOS != "windows",
Examples: []fs.OptionExample{{
Value: "true",
From e59801c69bc8a3d2b9614d9b8e7a25831d99e0bc Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 23:26:10 +0100
Subject: [PATCH 072/560] Add Maciej Radzikowski to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 91fe78693606b..d4abb2bab956b 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -610,3 +610,4 @@ put them back in again.` >}}
* Art M. Gallagher
* Sven Gerber <49589423+svengerber@users.noreply.github.com>
* CrossR
+ * Maciej Radzikowski
From c85fbebce6f7166350c79e11fae763c8264ef865 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 16 Jun 2022 22:55:45 +0100
Subject: [PATCH 073/560] s3: simplify PutObject code to use the
Request.SetStreamingBody method
In this commit
e5974ac4b0a847e8 s3: use PutObject from the aws SDK to upload single part objects
rclone was made to upload objects to s3 using PUT requests rather than
using signed uploads.
However this change missed the fact that there is a supported way to
do this in the SDK using the SetStreamingBody method on the Request.
This therefore reverts a lot of the previous commit to do with making
an unsigned connection and other complication and uses the SDK
facility.
---
backend/s3/s3.go | 107 ++++++++++++++---------------------------------
1 file changed, 32 insertions(+), 75 deletions(-)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index dd894db18156a..4e2a15cdeed93 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net/http"
"net/url"
"path"
@@ -32,7 +33,6 @@ import (
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
- v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/ncw/swift/v2"
"github.com/rclone/rclone/fs"
@@ -2044,7 +2044,6 @@ type Fs struct {
ctx context.Context // global context for reading config
features *fs.Features // optional features
c *s3.S3 // the connection to the s3 server
- cu *s3.S3 // unsigned connection to the s3 server for PutObject
ses *session.Session // the s3 session
rootBucket string // bucket part of root (if any)
rootDirectory string // directory part of root (if any)
@@ -2181,11 +2180,7 @@ func getClient(ctx context.Context, opt *Options) *http.Client {
}
// s3Connection makes a connection to s3
-//
-// If unsignedBody is set then the connection is configured for
-// unsigned bodies which is necessary for PutObject if we don't want
-// it to seek
-func s3Connection(ctx context.Context, opt *Options, client *http.Client) (*s3.S3, *s3.S3, *session.Session, error) {
+func s3Connection(ctx context.Context, opt *Options, client *http.Client) (*s3.S3, *session.Session, error) {
ci := fs.GetConfig(ctx)
// Make the auth
v := credentials.Value{
@@ -2202,7 +2197,7 @@ func s3Connection(ctx context.Context, opt *Options, client *http.Client) (*s3.S
// start a new AWS session
awsSession, err := session.NewSession()
if err != nil {
- return nil, nil, nil, fmt.Errorf("NewSession: %w", err)
+ return nil, nil, fmt.Errorf("NewSession: %w", err)
}
// first provider to supply a credential set "wins"
@@ -2242,9 +2237,9 @@ func s3Connection(ctx context.Context, opt *Options, client *http.Client) (*s3.S
// if no access key/secret and iam is explicitly disabled then fall back to anon interaction
cred = credentials.AnonymousCredentials
case v.AccessKeyID == "":
- return nil, nil, nil, errors.New("access_key_id not found")
+ return nil, nil, errors.New("access_key_id not found")
case v.SecretAccessKey == "":
- return nil, nil, nil, errors.New("secret_access_key not found")
+ return nil, nil, errors.New("secret_access_key not found")
}
if opt.Region == "" {
@@ -2283,36 +2278,25 @@ func s3Connection(ctx context.Context, opt *Options, client *http.Client) (*s3.S
// (from the shared config file) if the passed-in Options.Config.Credentials is nil.
awsSessionOpts.Config.Credentials = nil
}
- // Setting this stops PutObject reading the body twice and seeking
- // We add our own Content-MD5 for data protection
- awsSessionOpts.Config.S3DisableContentMD5Validation = aws.Bool(true)
ses, err := session.NewSessionWithOptions(awsSessionOpts)
if err != nil {
- return nil, nil, nil, err
- }
- newC := func(unsignedBody bool) *s3.S3 {
- c := s3.New(ses)
- if opt.V2Auth || opt.Region == "other-v2-signature" {
- fs.Debugf(nil, "Using v2 auth")
- signer := func(req *request.Request) {
- // Ignore AnonymousCredentials object
- if req.Config.Credentials == credentials.AnonymousCredentials {
- return
- }
- sign(v.AccessKeyID, v.SecretAccessKey, req.HTTPRequest)
+ return nil, nil, err
+ }
+ c := s3.New(ses)
+ if opt.V2Auth || opt.Region == "other-v2-signature" {
+ fs.Debugf(nil, "Using v2 auth")
+ signer := func(req *request.Request) {
+ // Ignore AnonymousCredentials object
+ if req.Config.Credentials == credentials.AnonymousCredentials {
+ return
}
- c.Handlers.Sign.Clear()
- c.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
- c.Handlers.Sign.PushBack(signer)
- } else if unsignedBody {
- // If the body is unsigned then tell the signer that we aren't signing the payload
- c.Handlers.Sign.Clear()
- c.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
- c.Handlers.Sign.PushBackNamed(v4.BuildNamedHandler("v4.SignRequestHandler.WithUnsignedPayload", v4.WithUnsignedPayload))
+ sign(v.AccessKeyID, v.SecretAccessKey, req.HTTPRequest)
}
- return c
+ c.Handlers.Sign.Clear()
+ c.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
+ c.Handlers.Sign.PushBack(signer)
}
- return newC(false), newC(true), ses, nil
+ return c, ses, nil
}
func checkUploadChunkSize(cs fs.SizeSuffix) error {
@@ -2501,7 +2485,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
opt.SSECustomerKeyMD5 = base64.StdEncoding.EncodeToString(md5sumBinary[:])
}
srv := getClient(ctx, opt)
- c, cu, ses, err := s3Connection(ctx, opt, srv)
+ c, ses, err := s3Connection(ctx, opt, srv)
if err != nil {
return nil, err
}
@@ -2519,7 +2503,6 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
ci: ci,
ctx: ctx,
c: c,
- cu: cu,
ses: ses,
pacer: pc,
cache: bucket.NewCache(),
@@ -2643,12 +2626,11 @@ func (f *Fs) updateRegionForBucket(ctx context.Context, bucket string) error {
// Make a new session with the new region
oldRegion := f.opt.Region
f.opt.Region = region
- c, cu, ses, err := s3Connection(f.ctx, &f.opt, f.srv)
+ c, ses, err := s3Connection(f.ctx, &f.opt, f.srv)
if err != nil {
return fmt.Errorf("creating new session failed: %w", err)
}
f.c = c
- f.cu = cu
f.ses = ses
fs.Logf(f, "Switched region to %q from %q", region, oldRegion)
@@ -4143,48 +4125,23 @@ func (o *Object) uploadMultipart(ctx context.Context, req *s3.PutObjectInput, si
return etag, nil
}
-// unWrapAwsError unwraps AWS errors, looking for a non AWS error
-//
-// It returns true if one was found and the error, or false and the
-// error passed in.
-func unWrapAwsError(err error) (found bool, outErr error) {
- if awsErr, ok := err.(awserr.Error); ok {
- var origErrs []error
- if batchErr, ok := awsErr.(awserr.BatchError); ok {
- origErrs = batchErr.OrigErrs()
- } else {
- origErrs = []error{awsErr.OrigErr()}
- }
- for _, origErr := range origErrs {
- found, newErr := unWrapAwsError(origErr)
- if found {
- return found, newErr
- }
- }
- return false, err
- }
- return true, err
-}
-
// Upload a single part using PutObject
func (o *Object) uploadSinglepartPutObject(ctx context.Context, req *s3.PutObjectInput, size int64, in io.Reader) (etag string, lastModified time.Time, err error) {
- req.Body = readers.NewFakeSeeker(in, size)
- var resp *s3.PutObjectOutput
+ r, resp := o.fs.c.PutObjectRequest(req)
+ if req.ContentLength != nil && *req.ContentLength == 0 {
+ // Can't upload zero length files like this for some reason
+ r.Body = bytes.NewReader([]byte{})
+ } else {
+ r.SetStreamingBody(ioutil.NopCloser(in))
+ }
+ r.SetContext(ctx)
+ r.HTTPRequest.Header.Set("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD")
+
err = o.fs.pacer.CallNoRetry(func() (bool, error) {
- resp, err = o.fs.cu.PutObject(req)
+ err := r.Send()
return o.fs.shouldRetry(ctx, err)
})
if err != nil {
- // Return the underlying error if we have a Serialization error if possible
- //
- // Serialization errors are synthesized locally in the SDK (not returned from the
- // server). We'll get one if the SDK attempts a retry, however the FakeSeeker will
- // remember the previous error from Read and return that.
- if do, ok := err.(awserr.Error); ok && do.Code() == request.ErrCodeSerialization {
- if found, newErr := unWrapAwsError(err); found {
- err = newErr
- }
- }
return etag, lastModified, err
}
lastModified = time.Now()
From dcc128c70d2bf4f46304e1befb3fdbbdeebd86fa Mon Sep 17 00:00:00 2001
From: Lu Wang
Date: Fri, 17 Jun 2022 07:33:56 +0200
Subject: [PATCH 074/560] docs/onedrive: document creation of client ID for
OneDrive Business
---
docs/content/onedrive.md | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/docs/content/onedrive.md b/docs/content/onedrive.md
index a847d2a3879aa..f25bbe513ca22 100644
--- a/docs/content/onedrive.md
+++ b/docs/content/onedrive.md
@@ -120,13 +120,15 @@ To copy a local directory to an OneDrive directory called backup
### Getting your own Client ID and Key
-You can use your own Client ID if the default (`client_id` left blank)
-one doesn't work for you or you see lots of throttling. The default
-Client ID and Key is shared by all rclone users when performing
-requests.
+rclone uses a default Client ID when talking to OneDrive, unless a custom `client_id` is specified in the config.
+The default Client ID and Key are shared by all rclone users when performing requests.
-If you are having problems with them (E.g., seeing a lot of throttling), you can get your own
-Client ID and Key by following the steps below:
+You may choose to create and use your own Client ID, in case the default one does not work well for you.
+For example, you might see throtting.
+
+#### Creating Client ID for OneDrive Personal
+
+To create your own Client ID, please follow these steps:
1. Open https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade and then click `New registration`.
2. Enter a name for your app, choose account type `Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)`, select `Web` in `Redirect URI`, then type (do not copy and paste) `http://localhost:53682/` and click Register. Copy and keep the `Application (client) ID` under the app name for later use.
@@ -142,6 +144,22 @@ See [Microsoft Docs](https://docs.microsoft.com/en-us/graph/permissions-referenc
The `Sites.Read.All` permission is required if you need to [search SharePoint sites when configuring the remote](https://github.com/rclone/rclone/pull/5883). However, if that permission is not assigned, you need to exclude `Sites.Read.All` from your access scopes or set `disable_site_permission` option to true in the advanced options.
+#### Creating Client ID for OneDrive Business
+
+The steps for OneDrive Personal may or may not work for OneDrive Business, depending on the security settings of the organization.
+A common error is that the publisher of the App is not verified.
+
+You may try to [verify you account](https://docs.microsoft.com/en-us/azure/active-directory/develop/publisher-verification-overview), or try to limit the App to your organization only, as shown below.
+
+1. Make sure to create the App with your business account.
+2. Follow the steps above to create an App. However, we need a different account type here: `Accounts in this organizational directory only (*** - Single tenant)`. Note that you can also change the account type aftering creating the App.
+3. Find the [tenant ID](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant) of your organization.
+4. In the rclone config, set `auth_url` to `https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/authorize`.
+5. In the rclone config, set `token_url` to `https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token`.
+
+Note: If you have a special region, you may need a different host in step 4 and 5. Here are [some hints](https://github.com/rclone/rclone/blob/bc23bf11db1c78c6ebbf8ea538fbebf7058b4176/backend/onedrive/onedrive.go#L86).
+
+
### Modification time and hashes
OneDrive allows modification times to be set on objects accurate to 1
From 295006f66226b6550cb9a936d98ff72519eaecc8 Mon Sep 17 00:00:00 2001
From: Scott Grimes
Date: Mon, 24 Jan 2022 16:53:55 -0500
Subject: [PATCH 075/560] opendrive: resolve lag and truncate bugs fixes #5936
Co-authored-by: buengese
---
backend/opendrive/opendrive.go | 54 ++++++++++++++++++++++++++--------
1 file changed, 41 insertions(+), 13 deletions(-)
diff --git a/backend/opendrive/opendrive.go b/backend/opendrive/opendrive.go
index 6e5967e626c1c..ac1a1c01df8ab 100644
--- a/backend/opendrive/opendrive.go
+++ b/backend/opendrive/opendrive.go
@@ -362,7 +362,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
srcPath := srcObj.fs.rootSlash() + srcObj.remote
dstPath := f.rootSlash() + remote
if strings.EqualFold(srcPath, dstPath) {
- return nil, fmt.Errorf("Can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
+ return nil, fmt.Errorf("can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
}
// Create temporary object
@@ -429,6 +429,12 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
return nil, err
}
+ // move_copy will silently truncate new filenames
+ if len(leaf) > 255 {
+ fs.Debugf(src, "Can't move file: name (%q) exceeds 255 char", leaf)
+ return nil, fs.ErrorFileNameTooLong
+ }
+
// Copy the object
var resp *http.Response
response := moveCopyFileResponse{}
@@ -1024,30 +1030,52 @@ func (o *Object) readMetaData(ctx context.Context) (err error) {
return err
}
var resp *http.Response
- folderList := FolderList{}
- err = o.fs.pacer.Call(func() (bool, error) {
+ fileInfo := File{}
+
+ // If we know the object id perform a direct lookup
+ // because the /folder/itembyname.json endpoint is unreliable:
+ // newly created objects take an arbitrary amount of time to show up
+ if o.id != "" {
opts := rest.Opts{
Method: "GET",
- Path: fmt.Sprintf("/folder/itembyname.json/%s/%s?name=%s",
- o.fs.session.SessionID, directoryID, url.QueryEscape(o.fs.opt.Enc.FromStandardName(leaf))),
+ Path: fmt.Sprintf("/file/info.json/%s?session_id=%s",
+ o.id, o.fs.session.SessionID),
+ }
+ err = o.fs.pacer.Call(func() (bool, error) {
+ resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &fileInfo)
+ return o.fs.shouldRetry(ctx, resp, err)
+ })
+ if err != nil {
+ return fmt.Errorf("failed to get fileinfo: %w", err)
}
+
+ o.id = fileInfo.FileID
+ o.modTime = time.Unix(fileInfo.DateModified, 0)
+ o.md5 = fileInfo.FileHash
+ o.size = fileInfo.Size
+ return nil
+ }
+ folderList := FolderList{}
+ opts := rest.Opts{
+ Method: "GET",
+ Path: fmt.Sprintf("/folder/itembyname.json/%s/%s?name=%s",
+ o.fs.session.SessionID, directoryID, url.QueryEscape(o.fs.opt.Enc.FromStandardName(leaf))),
+ }
+ err = o.fs.pacer.Call(func() (bool, error) {
resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &folderList)
return o.fs.shouldRetry(ctx, resp, err)
})
if err != nil {
return fmt.Errorf("failed to get folder list: %w", err)
}
-
if len(folderList.Files) == 0 {
return fs.ErrorObjectNotFound
}
-
- leafFile := folderList.Files[0]
- o.id = leafFile.FileID
- o.modTime = time.Unix(leafFile.DateModified, 0)
- o.md5 = leafFile.FileHash
- o.size = leafFile.Size
-
+ fileInfo = folderList.Files[0]
+ o.id = fileInfo.FileID
+ o.modTime = time.Unix(fileInfo.DateModified, 0)
+ o.md5 = fileInfo.FileHash
+ o.size = fileInfo.Size
return nil
}
From 8e2d9a4cb9e857514ad0c235612b42f4ec244990 Mon Sep 17 00:00:00 2001
From: Phil Shackleton <71221528+philshacks@users.noreply.github.com>
Date: Fri, 17 Jun 2022 16:50:01 +0100
Subject: [PATCH 076/560] drive: update Internal OAuth consent screen docs
Updated instructions in `Making your own client_id` section to record process for selecting "Internal" OAuth consent screen.
---
docs/content/drive.md | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/docs/content/drive.md b/docs/content/drive.md
index a2bf6cfb11c53..871fc50ef6b09 100644
--- a/docs/content/drive.md
+++ b/docs/content/drive.md
@@ -1458,8 +1458,9 @@ enter "Developer Contact Email" (your own email is OK); then click on "Save" (al
Click again on "Credentials" on the left panel to go back to the
"Credentials" screen.
-(PS: if you are a GSuite user, you could also select "Internal" instead
-of "External" above, but this has not been tested/documented so far).
+ (PS: if you are a GSuite user, you could also select "Internal" instead
+of "External" above, but this will restrict API use to Google Workspace
+users in your organisation).
6. Click on the "+ CREATE CREDENTIALS" button at the top of the screen,
then select "OAuth client ID".
@@ -1467,14 +1468,18 @@ then select "OAuth client ID".
7. Choose an application type of "Desktop app" and click "Create". (the default name is fine)
8. It will show you a client ID and client secret. Make a note of these.
+
+ (If you selected "External" at Step 5 continue to "Publish App" in the Steps 9 and 10.
+ If you chose "Internal" you don't need to publish and can skip straight to
+ Step 11.)
9. Go to "Oauth consent screen" and press "Publish App"
-10. Provide the noted client ID and client secret to rclone.
-
-11. Click "OAuth consent screen", then click "PUBLISH APP" button and
+10. Click "OAuth consent screen", then click "PUBLISH APP" button and
confirm, or add your account under "Test users".
+11. Provide the noted client ID and client secret to rclone.
+
Be aware that, due to the "enhanced security" recently introduced by
Google, you are theoretically expected to "submit your app for verification"
and then wait a few weeks(!) for their response; in practice, you can go right
From 2fac8fdde6480af6a8ab55d8036eb3b4188c50db Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Fri, 17 Jun 2022 16:52:17 +0100
Subject: [PATCH 077/560] Add Scott Grimes to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index d4abb2bab956b..ef130e6aa17e1 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -611,3 +611,4 @@ put them back in again.` >}}
* Sven Gerber <49589423+svengerber@users.noreply.github.com>
* CrossR
* Maciej Radzikowski
+ * Scott Grimes
From f829ded4567259db8585d5a73895b48adf8bb517 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Fri, 17 Jun 2022 16:52:17 +0100
Subject: [PATCH 078/560] Add Phil Shackleton to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index ef130e6aa17e1..04322ca1d9ddc 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -612,3 +612,4 @@ put them back in again.` >}}
* CrossR
* Maciej Radzikowski
* Scott Grimes
+ * Phil Shackleton <71221528+philshacks@users.noreply.github.com>
From f7c36ce0f9473bd065d143f3f7c1015c02bcaf08 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Fri, 17 Jun 2022 14:34:57 +0100
Subject: [PATCH 079/560] s3: unwrap SDK errors to reveal underlying errors on
upload
The SDK doesn't wrap errors in a Go standard way so they can't be
unwrapped and tested for - eg fatal error.
The code looks for a Serialization or RequestError and returns the
unwrapped underlying error if possible.
This fixes the fs/operations integration tests checking for fatal
errors being returned.
---
backend/s3/s3.go | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index 4e2a15cdeed93..c81024ce3c60c 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -4125,6 +4125,29 @@ func (o *Object) uploadMultipart(ctx context.Context, req *s3.PutObjectInput, si
return etag, nil
}
+// unWrapAwsError unwraps AWS errors, looking for a non AWS error
+//
+// It returns true if one was found and the error, or false and the
+// error passed in.
+func unWrapAwsError(err error) (found bool, outErr error) {
+ if awsErr, ok := err.(awserr.Error); ok {
+ var origErrs []error
+ if batchErr, ok := awsErr.(awserr.BatchError); ok {
+ origErrs = batchErr.OrigErrs()
+ } else {
+ origErrs = []error{awsErr.OrigErr()}
+ }
+ for _, origErr := range origErrs {
+ found, newErr := unWrapAwsError(origErr)
+ if found {
+ return found, newErr
+ }
+ }
+ return false, err
+ }
+ return true, err
+}
+
// Upload a single part using PutObject
func (o *Object) uploadSinglepartPutObject(ctx context.Context, req *s3.PutObjectInput, size int64, in io.Reader) (etag string, lastModified time.Time, err error) {
r, resp := o.fs.c.PutObjectRequest(req)
@@ -4142,6 +4165,17 @@ func (o *Object) uploadSinglepartPutObject(ctx context.Context, req *s3.PutObjec
return o.fs.shouldRetry(ctx, err)
})
if err != nil {
+ // Return the underlying error if we have a
+ // Serialization or RequestError error if possible
+ //
+ // These errors are synthesized locally in the SDK
+ // (not returned from the server) and we'd rather have
+ // the underlying error if there is one.
+ if do, ok := err.(awserr.Error); ok && (do.Code() == request.ErrCodeSerialization || do.Code() == request.ErrCodeRequestError) {
+ if found, newErr := unWrapAwsError(err); found {
+ err = newErr
+ }
+ }
return etag, lastModified, err
}
lastModified = time.Now()
From b9de37af80a6f794169a7029e460987149bcd28f Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Fri, 17 Jun 2022 15:02:49 +0100
Subject: [PATCH 080/560] test_all: Only run backend tests for Internet Archive
as it is too slow
---
fstest/test_all/config.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fstest/test_all/config.yaml b/fstest/test_all/config.yaml
index f0abff00739e9..3588cdddc71b2 100644
--- a/fstest/test_all/config.yaml
+++ b/fstest/test_all/config.yaml
@@ -139,6 +139,8 @@ backends:
- backend: "internetarchive"
remote: "TestIA:rclone-integration-test"
fastlist: true
+ tests:
+ - backend
- backend: "jottacloud"
remote: "TestJottacloud:"
fastlist: true
From 100acc570aa42d4943d605ea007789ae0fd4f574 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sat, 18 Jun 2022 15:05:12 +0100
Subject: [PATCH 081/560] test_all: fix -clean so it works on remotes with
paths
---
fstest/test_all/clean.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fstest/test_all/clean.go b/fstest/test_all/clean.go
index c4d22a8a41d34..c12a4839abd72 100644
--- a/fstest/test_all/clean.go
+++ b/fstest/test_all/clean.go
@@ -9,6 +9,7 @@ import (
"regexp"
"github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fs/fspath"
"github.com/rclone/rclone/fs/list"
"github.com/rclone/rclone/fs/operations"
)
@@ -39,7 +40,7 @@ func cleanFs(ctx context.Context, remote string, cleanup bool) error {
}
err = entries.ForDirError(func(dir fs.Directory) error {
dirPath := dir.Remote()
- fullPath := remote + dirPath
+ fullPath := fspath.JoinRootPath(remote, dirPath)
if MatchTestRemote.MatchString(dirPath) {
if *dryRun {
log.Printf("Not Purging %s - -dry-run", fullPath)
From 14e0396fcb353a2ff5e533aeea1da2307bb81d68 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sat, 18 Jun 2022 15:13:18 +0100
Subject: [PATCH 082/560] test_all: allow internet archive backend more time
---
fstest/test_all/config.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/fstest/test_all/config.yaml b/fstest/test_all/config.yaml
index 3588cdddc71b2..68f17c2ddd0a5 100644
--- a/fstest/test_all/config.yaml
+++ b/fstest/test_all/config.yaml
@@ -141,6 +141,7 @@ backends:
fastlist: true
tests:
- backend
+ extratime: 2.0
- backend: "jottacloud"
remote: "TestJottacloud:"
fastlist: true
From 36add0afbfb908796a3f3ea6295b9b459a4ac568 Mon Sep 17 00:00:00 2001
From: eNV25
Date: Sun, 19 Jun 2022 14:22:45 +0400
Subject: [PATCH 083/560] ncdu: replace termbox with tcell's termbox wrapper
The https://github.com/nsf/termbox-go library is no longer maintained
so this change replaces it with the maintained
github.com/gdamore/tcell library which has a termbox backwards
compatibility layer.
There are a few minor changes from the termbox library:
- Using Clear with fg bg ColorDefault resulted in a white background for some reason.
- Clear with fg ColorWhite bg ColorBlack was used instead.
- tcell's termbox wrapper doesn't support ColorLightYellow.
- ColorYellow + 8 was used instead.
---
cmd/ncdu/ncdu.go | 16 +++++++---------
cmd/ncdu/ncdu_unsupported.go | 4 ++--
go.mod | 2 +-
go.sum | 11 ++++++++---
4 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go
index 963ddb628df15..aea2501eb7a12 100644
--- a/cmd/ncdu/ncdu.go
+++ b/cmd/ncdu/ncdu.go
@@ -1,7 +1,7 @@
// Package ncdu implements a text based user interface for exploring a remote
-//go:build !plan9 && !solaris && !js
-// +build !plan9,!solaris,!js
+//go:build !plan9 && !js
+// +build !plan9,!js
package ncdu
@@ -14,8 +14,8 @@ import (
"strings"
"github.com/atotto/clipboard"
+ "github.com/gdamore/tcell/v2/termbox"
runewidth "github.com/mattn/go-runewidth"
- termbox "github.com/nsf/termbox-go"
"github.com/rclone/rclone/cmd"
"github.com/rclone/rclone/cmd/ncdu/scan"
"github.com/rclone/rclone/fs"
@@ -333,10 +333,7 @@ func (u *UI) Draw() error {
u.dirListHeight = h - 3
// Plot
- err := termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
- if err != nil {
- return fmt.Errorf("failed to clear screen: %w", err)
- }
+ termbox.Clear(termbox.ColorWhite, termbox.ColorBlack)
// Header line
Linef(0, 0, w, termbox.ColorBlack, termbox.ColorWhite, ' ', "rclone ncdu %s - use the arrow keys to navigate, press ? for help", fs.Version)
@@ -374,8 +371,9 @@ func (u *UI) Draw() error {
if err != nil {
fg = termbox.ColorRed
}
+ const colorLightYellow = termbox.ColorYellow + 8
if isSelected {
- fg = termbox.ColorLightYellow
+ fg = colorLightYellow
}
bg := termbox.ColorBlack
if n == dirPos.entry {
@@ -456,7 +454,7 @@ func (u *UI) Draw() error {
if u.showBox {
u.Box()
}
- err = termbox.Flush()
+ err := termbox.Flush()
if err != nil {
return fmt.Errorf("failed to flush screen: %w", err)
}
diff --git a/cmd/ncdu/ncdu_unsupported.go b/cmd/ncdu/ncdu_unsupported.go
index 6f66cfb6c9cb9..b4a9b22cf0d63 100644
--- a/cmd/ncdu/ncdu_unsupported.go
+++ b/cmd/ncdu/ncdu_unsupported.go
@@ -1,7 +1,7 @@
// Build for ncdu for unsupported platforms to stop go complaining
// about "no buildable Go source files "
-//go:build plan9 || solaris || js
-// +build plan9 solaris js
+//go:build plan9 || js
+// +build plan9 js
package ncdu
diff --git a/go.mod b/go.mod
index 75a242ff7a701..1fe102fd68853 100644
--- a/go.mod
+++ b/go.mod
@@ -24,6 +24,7 @@ require (
github.com/dop251/scsu v0.0.0-20220106150536-84ac88021d00
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.4
github.com/gabriel-vasile/mimetype v1.4.0
+ github.com/gdamore/tcell/v2 v2.5.1
github.com/go-chi/chi/v5 v5.0.7
github.com/google/uuid v1.3.0
github.com/hanwen/go-fuse/v2 v2.1.0
@@ -38,7 +39,6 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/ncw/go-acd v0.0.0-20201019170801-fe55f33415b1
github.com/ncw/swift/v2 v2.0.1
- github.com/nsf/termbox-go v1.1.1
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/sftp v1.13.5-0.20211228200725-31aac3e1878d
github.com/pmezard/go-difflib v1.0.0
diff --git a/go.sum b/go.sum
index 8d64ccdfae170..e957043f89908 100644
--- a/go.sum
+++ b/go.sum
@@ -191,6 +191,10 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
+github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
+github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
+github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I=
+github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
@@ -403,6 +407,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
@@ -418,7 +424,6 @@ github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqf
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -444,8 +449,6 @@ github.com/ncw/swift/v2 v2.0.1/go.mod h1:z0A9RVdYPjNjXVo2pDOPxZ4eu3oarO1P91fTItc
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
-github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
-github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@@ -877,12 +880,14 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
From 21c746a56c86f20dec639afa49f321cc6c6e38b1 Mon Sep 17 00:00:00 2001
From: buengese
Date: Sat, 18 Jun 2022 01:39:44 +0200
Subject: [PATCH 084/560] fichier: parse api error codes and them accordingly
---
backend/fichier/api.go | 43 ++++++++++++++++++++++++++++++------------
1 file changed, 31 insertions(+), 12 deletions(-)
diff --git a/backend/fichier/api.go b/backend/fichier/api.go
index 6a17462658dee..15c916c736314 100644
--- a/backend/fichier/api.go
+++ b/backend/fichier/api.go
@@ -28,25 +28,44 @@ var retryErrorCodes = []int{
509, // Bandwidth Limit Exceeded
}
+var errorRegex = regexp.MustCompile(`#\d{1,3}`)
+
+func parseFichierError(err error) int {
+ matches := errorRegex.FindStringSubmatch(err.Error())
+ if len(matches) == 0 {
+ return 0
+ }
+ code, err := strconv.Atoi(matches[0])
+ if err != nil {
+ fs.Debugf(nil, "failed parsing fichier error: %v", err)
+ return 0
+ }
+ return code
+}
+
// shouldRetry returns a boolean as to whether this resp and err
// deserve to be retried. It returns the err as a convenience
func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, error) {
if fserrors.ContextError(ctx, &err) {
return false, err
}
- // Detect this error which the integration tests provoke
- // error HTTP error 403 (403 Forbidden) returned body: "{\"message\":\"Flood detected: IP Locked #374\",\"status\":\"KO\"}"
- //
- // https://1fichier.com/api.html
- //
- // file/ls.cgi is limited :
+ // 1Fichier uses HTTP error code 403 (Forbidden) for all kinds of errors with
+ // responses looking like this: "{\"message\":\"Flood detected: IP Locked #374\",\"status\":\"KO\"}"
//
- // Warning (can be changed in case of abuses) :
- // List all files of the account is limited to 1 request per hour.
- // List folders is limited to 5 000 results and 1 request per folder per 30s.
- if err != nil && strings.Contains(err.Error(), "Flood detected") {
- fs.Debugf(nil, "Sleeping for 30 seconds due to: %v", err)
- time.Sleep(30 * time.Second)
+ // We attempt to parse the actual 1Fichier error code from this body and handle it accordingly
+ // Most importantly #374 (Flood detected: IP locked) which the integration tests provoke
+ // The list below is far from complete and should be expanded if we see any more error codes.
+ if err != nil {
+ switch parseFichierError(err) {
+ case 93:
+ return false, err // No such user
+ case 186:
+ return false, err // IP blocked?
+ case 374:
+ fs.Debugf(nil, "Sleeping for 30 seconds due to: %v", err)
+ time.Sleep(30 * time.Second)
+ default:
+ }
}
return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
}
From f2a15a174f8cdd21950b123f102ec9f9105098e1 Mon Sep 17 00:00:00 2001
From: Caleb
Date: Sun, 19 Jun 2022 07:26:53 -0600
Subject: [PATCH 085/560] docs: grammatical clarification in sync docs
---
docs/content/commands/rclone_sync.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/content/commands/rclone_sync.md b/docs/content/commands/rclone_sync.md
index 57e24dd2d9e68..5596240e1e9a9 100644
--- a/docs/content/commands/rclone_sync.md
+++ b/docs/content/commands/rclone_sync.md
@@ -28,7 +28,7 @@ errors at any point. Duplicate objects (files with the same name, on
those providers that support it) are also not yet handled.
It is always the contents of the directory that is synced, not the
-directory so when source:path is a directory, it's the contents of
+directory itself. So when source:path is a directory, it's the contents of
source:path that are copied, not the directory name and contents. See
extended explanation in the `copy` command above if unsure.
From 8b8802a0784491e6afba628aa6f7fbe78d9303db Mon Sep 17 00:00:00 2001
From: J-P Treen
Date: Wed, 28 Jul 2021 17:05:21 +0100
Subject: [PATCH 086/560] copyurl: Add option to honor the HTTP header filename
directive. Implemented --header-filename for use with copyurl.
For specifically setting preferred download filenames for HTTP requests, RFC 6226
specifies a 'filename' directive, available within 'Content-Disposition'
header. We can handle with 'mime.ParseMediaType'.
See below for details:
https://httpwg.org/specs/rfc6266.html#disposition.parameter.filename
https://httpwg.org/specs/rfc6266.html#advice.generating
Co-authored-by: buengese
---
cmd/copyurl/copyurl.go | 21 ++++++++++++---------
fs/operations/operations.go | 21 ++++++++++++++++-----
fs/operations/operations_test.go | 12 ++++++------
fs/operations/rc.go | 3 ++-
4 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/cmd/copyurl/copyurl.go b/cmd/copyurl/copyurl.go
index e7e31f188835d..fc3cf38cccc86 100644
--- a/cmd/copyurl/copyurl.go
+++ b/cmd/copyurl/copyurl.go
@@ -14,16 +14,18 @@ import (
)
var (
- autoFilename = false
- printFilename = false
- stdout = false
- noClobber = false
+ autoFilename = false
+ headerFilename = false
+ printFilename = false
+ stdout = false
+ noClobber = false
)
func init() {
cmd.Root.AddCommand(commandDefinition)
cmdFlags := commandDefinition.Flags()
flags.BoolVarP(cmdFlags, &autoFilename, "auto-filename", "a", autoFilename, "Get the file name from the URL and use it for destination file path")
+ flags.BoolVarP(cmdFlags, &headerFilename, "header-filename", "", headerFilename, "Get the file name from the Content-Disposition header")
flags.BoolVarP(cmdFlags, &printFilename, "print-filename", "p", printFilename, "Print the resulting name from --auto-filename")
flags.BoolVarP(cmdFlags, &noClobber, "no-clobber", "", noClobber, "Prevent overwriting file with same name")
flags.BoolVarP(cmdFlags, &stdout, "stdout", "", stdout, "Write the output to stdout rather than a file")
@@ -36,10 +38,11 @@ var commandDefinition = &cobra.Command{
Download a URL's content and copy it to the destination without saving
it in temporary storage.
-Setting ` + "`--auto-filename`" + ` will cause the file name to be retrieved from
-the URL (after any redirections) and used in the destination
-path. With ` + "`--print-filename`" + ` in addition, the resulting file name will
-be printed.
+Setting ` + "`--auto-filename`" + ` will attempt to automatically determine the filename from the URL
+(after any redirections) and used in the destination path.
+With ` + "`--auto-filename-header`" + ` in
+addition, if a specific filename is set in HTTP headers, it will be used instead of the name from the URL.
+With ` + "`--print-filename`" + ` in addition, the resulting file name will be printed.
Setting ` + "`--no-clobber`" + ` will prevent overwriting file on the
destination if there is one with the same name.
@@ -69,7 +72,7 @@ will cause the output to be written to standard output.
if stdout {
err = operations.CopyURLToWriter(context.Background(), args[0], os.Stdout)
} else {
- dst, err = operations.CopyURL(context.Background(), fsdst, dstFileName, args[0], autoFilename, noClobber)
+ dst, err = operations.CopyURL(context.Background(), fsdst, dstFileName, args[0], autoFilename, headerFilename, noClobber)
if printFilename && err == nil && dst != nil {
fmt.Println(dst.Remote())
}
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index c21675443eb5a..5eccc44f719eb 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -11,6 +11,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "mime"
"net/http"
"os"
"path"
@@ -1757,7 +1758,7 @@ func RcatSize(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadClo
type copyURLFunc func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error)
// copyURLFn copies the data from the url to the function supplied
-func copyURLFn(ctx context.Context, dstFileName string, url string, dstFileNameFromURL bool, fn copyURLFunc) (err error) {
+func copyURLFn(ctx context.Context, dstFileName string, url string, autoFilename, dstFileNameFromHeader bool, fn copyURLFunc) (err error) {
client := fshttp.NewClient(ctx)
resp, err := client.Get(url)
if err != nil {
@@ -1771,7 +1772,17 @@ func copyURLFn(ctx context.Context, dstFileName string, url string, dstFileNameF
if err != nil {
modTime = time.Now()
}
- if dstFileNameFromURL {
+ if autoFilename {
+ if dstFileNameFromHeader {
+ _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
+ headerFilename := path.Base(strings.Replace(params["filename"], "\\", "/", -1))
+ if err != nil || headerFilename == "" {
+ return fmt.Errorf("copyurl failed: filename not found in the Content-Dispoition header")
+ }
+ fs.Debugf(headerFilename, "filename found in Content-Disposition header.")
+ return fn(ctx, headerFilename, resp.Body, resp.ContentLength, modTime)
+ }
+
dstFileName = path.Base(resp.Request.URL.Path)
if dstFileName == "." || dstFileName == "/" {
return fmt.Errorf("CopyURL failed: file name wasn't found in url")
@@ -1782,9 +1793,9 @@ func copyURLFn(ctx context.Context, dstFileName string, url string, dstFileNameF
}
// CopyURL copies the data from the url to (fdst, dstFileName)
-func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string, dstFileNameFromURL bool, noClobber bool) (dst fs.Object, err error) {
+func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string, autoFilename, dstFileNameFromHeader bool, noClobber bool) (dst fs.Object, err error) {
- err = copyURLFn(ctx, dstFileName, url, dstFileNameFromURL, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) {
+ err = copyURLFn(ctx, dstFileName, url, autoFilename, dstFileNameFromHeader, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) {
if noClobber {
_, err = fdst.NewObject(ctx, dstFileName)
if err == nil {
@@ -1799,7 +1810,7 @@ func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string, ds
// CopyURLToWriter copies the data from the url to the io.Writer supplied
func CopyURLToWriter(ctx context.Context, url string, out io.Writer) (err error) {
- return copyURLFn(ctx, "", url, false, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) {
+ return copyURLFn(ctx, "", url, false, false, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) {
_, err = io.Copy(out, in)
return err
})
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 10475562aef2d..545bcbe4dd21b 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -739,31 +739,31 @@ func TestCopyURL(t *testing.T) {
ts := httptest.NewServer(handler)
defer ts.Close()
- o, err := operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, false, false)
+ o, err := operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, false, false, false)
require.NoError(t, err)
assert.Equal(t, int64(len(contents)), o.Size())
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, nil, fs.ModTimeNotSupported)
// Check file clobbering
- _, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, false, true)
+ _, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, false, false, true)
require.Error(t, err)
// Check auto file naming
status = 0
urlFileName := "filename.txt"
- o, err = operations.CopyURL(ctx, r.Fremote, "", ts.URL+"/"+urlFileName, true, false)
+ o, err = operations.CopyURL(ctx, r.Fremote, "", ts.URL+"/"+urlFileName, true, false, false)
require.NoError(t, err)
assert.Equal(t, int64(len(contents)), o.Size())
assert.Equal(t, urlFileName, o.Remote())
// Check auto file naming when url without file name
- _, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, true, false)
+ _, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, true, false, false)
require.Error(t, err)
// Check an error is returned for a 404
status = http.StatusNotFound
- o, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, false, false)
+ o, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, false, false, false)
require.Error(t, err)
assert.Contains(t, err.Error(), "Not Found")
assert.Nil(t, o)
@@ -776,7 +776,7 @@ func TestCopyURL(t *testing.T) {
tss := httptest.NewTLSServer(handler)
defer tss.Close()
- o, err = operations.CopyURL(ctx, r.Fremote, "file2", tss.URL, false, false)
+ o, err = operations.CopyURL(ctx, r.Fremote, "file2", tss.URL, false, false, false)
require.NoError(t, err)
assert.Equal(t, int64(len(contents)), o.Size())
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file2, fstest.NewItem(urlFileName, contents, t1)}, nil, fs.ModTimeNotSupported)
diff --git a/fs/operations/rc.go b/fs/operations/rc.go
index de77dae0913ef..848b56ca10a22 100644
--- a/fs/operations/rc.go
+++ b/fs/operations/rc.go
@@ -274,8 +274,9 @@ func rcSingleCommand(ctx context.Context, in rc.Params, name string, noRemote bo
}
autoFilename, _ := in.GetBool("autoFilename")
noClobber, _ := in.GetBool("noClobber")
+ headerFilename, _ := in.GetBool("headerFilename")
- _, err = CopyURL(ctx, f, remote, url, autoFilename, noClobber)
+ _, err = CopyURL(ctx, f, remote, url, autoFilename, headerFilename, noClobber)
return nil, err
case "uploadfile":
From ac0dc9922ee9e5171f5e2d3e0f9a2e6efb807f78 Mon Sep 17 00:00:00 2001
From: buengese
Date: Sun, 19 Jun 2022 15:39:05 +0200
Subject: [PATCH 087/560] copyurl: add tests for the option to honor the HTTP
header filename directive
---
fs/operations/operations_test.go | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 545bcbe4dd21b..04b26e8b8819e 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -729,10 +729,15 @@ func TestCopyURL(t *testing.T) {
// check when reading from regular HTTP server
status := 0
+ nameHeader := false
+ headerFilename := "headerfilename.txt"
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if status != 0 {
http.Error(w, "an error ocurred", status)
}
+ if nameHeader {
+ w.Header().Set("Content-Disposition", `attachment; filename="folder\`+headerFilename+`"`)
+ }
_, err := w.Write([]byte(contents))
assert.NoError(t, err)
})
@@ -757,10 +762,22 @@ func TestCopyURL(t *testing.T) {
assert.Equal(t, int64(len(contents)), o.Size())
assert.Equal(t, urlFileName, o.Remote())
+ // Check header file naming
+ nameHeader = true
+ o, err = operations.CopyURL(ctx, r.Fremote, "", ts.URL, true, true, false)
+ require.NoError(t, err)
+ assert.Equal(t, int64(len(contents)), o.Size())
+ assert.Equal(t, headerFilename, o.Remote())
+
// Check auto file naming when url without file name
_, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, true, false, false)
require.Error(t, err)
+ // Check header file naming without header set
+ nameHeader = false
+ _, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, true, true, false)
+ require.Error(t, err)
+
// Check an error is returned for a 404
status = http.StatusNotFound
o, err = operations.CopyURL(ctx, r.Fremote, "file1", ts.URL, false, false, false)
@@ -779,7 +796,7 @@ func TestCopyURL(t *testing.T) {
o, err = operations.CopyURL(ctx, r.Fremote, "file2", tss.URL, false, false, false)
require.NoError(t, err)
assert.Equal(t, int64(len(contents)), o.Size())
- fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file2, fstest.NewItem(urlFileName, contents, t1)}, nil, fs.ModTimeNotSupported)
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file2, fstest.NewItem(urlFileName, contents, t1), fstest.NewItem(headerFilename, contents, t1)}, nil, fs.ModTimeNotSupported)
}
func TestCopyURLToWriter(t *testing.T) {
From 7a909ebfb0d100187fab7949455bf74b8ce07042 Mon Sep 17 00:00:00 2001
From: Martin Czygan <53705+miku@users.noreply.github.com>
Date: Mon, 20 Jun 2022 13:14:58 +0200
Subject: [PATCH 088/560] fs/cache: fix cache unpin
---
fs/cache/cache.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/cache/cache.go b/fs/cache/cache.go
index f595618881744..3bdc8d37fc1b5 100644
--- a/fs/cache/cache.go
+++ b/fs/cache/cache.go
@@ -112,7 +112,7 @@ func PinUntilFinalized(f fs.Fs, x interface{}) {
// Unpin f from the cache
func Unpin(f fs.Fs) {
createOnFirstUse()
- c.Pin(fs.ConfigString(f))
+ c.Unpin(fs.ConfigString(f))
}
// Get gets an fs.Fs named fsString either from the cache or creates it afresh
From d03fffdf8d9017361ecae703907d9b5ed7824ec6 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 21 Jun 2022 14:28:25 +0100
Subject: [PATCH 089/560] Add eNV25 to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 04322ca1d9ddc..dd8e186b9fec6 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -613,3 +613,4 @@ put them back in again.` >}}
* Maciej Radzikowski
* Scott Grimes
* Phil Shackleton <71221528+philshacks@users.noreply.github.com>
+ * eNV25
From 3d55b537c6dcd0309cd00609ce7442ab8046be80 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 21 Jun 2022 14:28:25 +0100
Subject: [PATCH 090/560] Add Caleb to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index dd8e186b9fec6..1f2e3ec8fc501 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -614,3 +614,4 @@ put them back in again.` >}}
* Scott Grimes
* Phil Shackleton <71221528+philshacks@users.noreply.github.com>
* eNV25
+ * Caleb
From ed92bf335d3649328b8b049c8b91ac0c65b861c3 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 21 Jun 2022 14:28:25 +0100
Subject: [PATCH 091/560] Add J-P Treen to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 1f2e3ec8fc501..45a272a092d4a 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -615,3 +615,4 @@ put them back in again.` >}}
* Phil Shackleton <71221528+philshacks@users.noreply.github.com>
* eNV25
* Caleb
+ * J-P Treen
From 99dfe1eeae466b37cf357c219ab985914b77c7ef Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 21 Jun 2022 14:28:25 +0100
Subject: [PATCH 092/560] Add Martin Czygan to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 45a272a092d4a..90ea4b9fb8cf2 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -616,3 +616,4 @@ put them back in again.` >}}
* eNV25
* Caleb
* J-P Treen
+ * Martin Czygan <53705+miku@users.noreply.github.com>
From e95dff2fa19677d926d576d96729bdf647aea46e Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 20 Jun 2022 11:38:10 +0100
Subject: [PATCH 093/560] drive: add backend commands exportformats and
importformats for debugging
---
backend/drive/drive.go | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index 2a4d0a64b61b4..b352b9004855e 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -3351,6 +3351,12 @@ attempted if possible.
Use the -i flag to see what would be copied before copying.
`,
+}, {
+ Name: "exportformats",
+ Short: "Dump the export formats for debug purposes",
+}, {
+ Name: "importformats",
+ Short: "Dump the import formats for debug purposes",
}}
// Command the backend to run a named command
@@ -3465,6 +3471,10 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
}
}
return nil, nil
+ case "exportformats":
+ return f.exportFormats(ctx), nil
+ case "importformats":
+ return f.importFormats(ctx), nil
default:
return nil, fs.ErrorCommandNotFound
}
From ea5bb7936685e9de0ee7365a08c1ed84f6c7a9a5 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 20 Jun 2022 11:39:45 +0100
Subject: [PATCH 094/560] drive: document export for google apps scripts better
This also adds some more mime types from the code.
See: https://forum.rclone.org/t/how-do-i-copy-google-apps-scripts-with-rclone/31373
---
docs/content/drive.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/docs/content/drive.md b/docs/content/drive.md
index 871fc50ef6b09..da17114093f03 100644
--- a/docs/content/drive.md
+++ b/docs/content/drive.md
@@ -509,23 +509,28 @@ represent the currently available conversions.
| Extension | Mime Type | Description |
| --------- |-----------| ------------|
+| bmp | image/bmp | Windows Bitmap format |
| csv | text/csv | Standard CSV format for Spreadsheets |
+| doc | application/msword | Classic Word file |
| docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document | Microsoft Office Document |
| epub | application/epub+zip | E-book format |
| html | text/html | An HTML Document |
| jpg | image/jpeg | A JPEG Image File |
-| json | application/vnd.google-apps.script+json | JSON Text Format |
+| json | application/vnd.google-apps.script+json | JSON Text Format for Google Apps scripts |
| odp | application/vnd.oasis.opendocument.presentation | Openoffice Presentation |
| ods | application/vnd.oasis.opendocument.spreadsheet | Openoffice Spreadsheet |
| ods | application/x-vnd.oasis.opendocument.spreadsheet | Openoffice Spreadsheet |
| odt | application/vnd.oasis.opendocument.text | Openoffice Document |
| pdf | application/pdf | Adobe PDF Format |
+| pjpeg | image/pjpeg | Progressive JPEG Image |
| png | image/png | PNG Image Format|
| pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation | Microsoft Office Powerpoint |
| rtf | application/rtf | Rich Text Format |
| svg | image/svg+xml | Scalable Vector Graphics Format |
| tsv | text/tab-separated-values | Standard TSV format for spreadsheets |
| txt | text/plain | Plain Text |
+| wmf | application/x-msmetafile | Windows Meta File |
+| xls | application/vnd.ms-excel | Classic Excel file |
| xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | Microsoft Office Spreadsheet |
| zip | application/zip | A ZIP file of HTML, Images CSS |
From bc705e14d8ea53931f26a089f4dea00652f206de Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 13 Jun 2022 13:45:19 +0100
Subject: [PATCH 095/560] vfscache: fix fatal error: sync: unlock of unlocked
mutex error
This message is a double panic and was actually caused by an assertion
panic in:
vfs/vfscache/downloaders/downloaders.go
This is triggered by the code added relatively recently to fix a bug
with renaming files:
ec72432cecdc4eee vfs: fix failed to _ensure cache internal error: downloaders is nil error
So it appears that item.o may be nil at this point.
This patch detects item.o being nil and fetches it again with NewObject.
Fixes #6190 Fixes #6235
---
vfs/vfscache/item.go | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/vfs/vfscache/item.go b/vfs/vfscache/item.go
index d6a6e20b8d215..2c81458709025 100644
--- a/vfs/vfscache/item.go
+++ b/vfs/vfscache/item.go
@@ -1131,6 +1131,17 @@ func (item *Item) _ensure(offset, size int64) (err error) {
// Downloaders can be nil here if the file has been
// renamed, so need to make some more downloaders
// OK to call downloaders constructor with item.mu held
+
+ // item.o can also be nil under some circumstances
+ // See: https://github.com/rclone/rclone/issues/6190
+ // See: https://github.com/rclone/rclone/issues/6235
+ if item.o == nil {
+ o, err := item.c.fremote.NewObject(context.Background(), item.name)
+ if err != nil {
+ return err
+ }
+ item.o = o
+ }
item.downloaders = downloaders.New(item, item.c.opt, item.name, item.o)
}
return item.downloaders.Download(r)
From 4b7dc35cf4c6d30aba2aed1390d71a2b27c1bc7e Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 15:43:13 +0200
Subject: [PATCH 096/560] Fix sync docs incorrect merge
This copies the changes from an autogenerated section in the following commit:
https://github.com/rclone/rclone/commit/f2a15a174f8cdd21950b123f102ec9f9105098e1
---
cmd/sync/sync.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go
index 0b253b041ff8c..2bb085073f309 100644
--- a/cmd/sync/sync.go
+++ b/cmd/sync/sync.go
@@ -40,7 +40,7 @@ errors at any point. Duplicate objects (files with the same name, on
those providers that support it) are also not yet handled.
It is always the contents of the directory that is synced, not the
-directory so when source:path is a directory, it's the contents of
+directory itself. So when source:path is a directory, it's the contents of
source:path that are copied, not the directory name and contents. See
extended explanation in the ` + "`" + `copy` + "`" + ` command above if unsure.
From de5ccaab8e1dcd820fee9551c35aa3bcc5cb72b0 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 15:51:37 +0200
Subject: [PATCH 097/560] docs: cross link doc pages for related commands
---
cmd/backend/backend.go | 2 +-
cmd/check/check.go | 4 +++
cmd/copy/copy.go | 9 +++--
cmd/copyto/copyto.go | 4 +--
cmd/cryptcheck/cryptcheck.go | 6 ++--
cmd/cryptdecode/cryptdecode.go | 2 +-
cmd/delete/delete.go | 10 +++---
cmd/hashsum/hashsum.go | 3 ++
cmd/md5sum/md5sum.go | 4 +++
cmd/move/move.go | 6 +++-
cmd/moveto/moveto.go | 2 +-
cmd/ncdu/ncdu.go | 4 +++
cmd/purge/purge.go | 7 ++--
cmd/rmdir/rmdir.go | 6 ++--
cmd/rmdirs/rmdirs.go | 13 +++----
cmd/serve/webdav/webdav.go | 5 ++-
cmd/sha1sum/sha1sum.go | 4 +++
cmd/sync/sync.go | 6 ++--
cmd/tree/tree.go | 8 +++--
docs/content/rc.md | 64 ++++++++++++++++------------------
fs/config/rc.go | 12 +++----
fs/operations/rc.go | 18 +++++-----
fs/sync/rc.go | 2 +-
23 files changed, 117 insertions(+), 84 deletions(-)
diff --git a/cmd/backend/backend.go b/cmd/backend/backend.go
index 936e903c20cd0..85b7e44bd7cf2 100644
--- a/cmd/backend/backend.go
+++ b/cmd/backend/backend.go
@@ -145,7 +145,7 @@ Run them with
The help below will explain what arguments each command takes.
-See [the "rclone backend" command](/commands/rclone_backend/) for more
+See the [backend](/commands/rclone_backend/) command for more
info on how to pass options and arguments.
These can be run on a running backend using the rc command
diff --git a/cmd/check/check.go b/cmd/check/check.go
index ec58deb220340..886c36a58fe5e 100644
--- a/cmd/check/check.go
+++ b/cmd/check/check.go
@@ -139,6 +139,10 @@ Checks the files in the source and destination match. It compares
sizes and hashes (MD5 or SHA1) and logs a report of files that don't
match. It doesn't alter the source or destination.
+For the [crypt](/crypt/) remote there is a dedicated command,
+[cryptcheck](/commands/rclone_cryptcheck/), that are able to check
+the checksums of the crypted files.
+
If you supply the |--size-only| flag, it will only compare the sizes not
the hashes as well. Use this for a quick check.
diff --git a/cmd/copy/copy.go b/cmd/copy/copy.go
index ba2815852c10b..33175e1f77416 100644
--- a/cmd/copy/copy.go
+++ b/cmd/copy/copy.go
@@ -28,13 +28,18 @@ var commandDefinition = &cobra.Command{
Long: strings.ReplaceAll(`
Copy the source to the destination. Does not transfer files that are
identical on source and destination, testing by size and modification
-time or MD5SUM. Doesn't delete files from the destination.
+time or MD5SUM. Doesn't delete files from the destination. If you
+want to also delete files from destination, to make it match source,
+use the [sync](/commands/rclone_sync/) command instead.
Note that it is always the contents of the directory that is synced,
-not the directory so when source:path is a directory, it's the
+not the directory itself. So when source:path is a directory, it's the
contents of source:path that are copied, not the directory name and
contents.
+To copy single files, use the [copyto](/commands/rclone_copyto/)
+command instead.
+
If dest:path doesn't exist, it is created and the source:path contents
go there.
diff --git a/cmd/copyto/copyto.go b/cmd/copyto/copyto.go
index ed6ff7b1637d8..02d2af21e2ea4 100644
--- a/cmd/copyto/copyto.go
+++ b/cmd/copyto/copyto.go
@@ -21,8 +21,8 @@ If source:path is a file or directory then it copies it to a file or
directory named dest:path.
This can be used to upload single files to other than their current
-name. If the source is a directory then it acts exactly like the copy
-command.
+name. If the source is a directory then it acts exactly like the
+[copy](/commands/rclone_copy/) command.
So
diff --git a/cmd/cryptcheck/cryptcheck.go b/cmd/cryptcheck/cryptcheck.go
index 7ef0e86a40446..ec0597da73018 100644
--- a/cmd/cryptcheck/cryptcheck.go
+++ b/cmd/cryptcheck/cryptcheck.go
@@ -23,9 +23,9 @@ var commandDefinition = &cobra.Command{
Use: "cryptcheck remote:path cryptedremote:path",
Short: `Cryptcheck checks the integrity of a crypted remote.`,
Long: `
-rclone cryptcheck checks a remote against a crypted remote. This is
-the equivalent of running rclone check, but able to check the
-checksums of the crypted remote.
+rclone cryptcheck checks a remote against a [crypted](/crypt/) remote.
+This is the equivalent of running rclone [check](/commands/rclone_check/),
+but able to check the checksums of the crypted remote.
For it to work the underlying remote of the cryptedremote must support
some kind of checksum.
diff --git a/cmd/cryptdecode/cryptdecode.go b/cmd/cryptdecode/cryptdecode.go
index bdfd3e22127bd..27ab93df131db 100644
--- a/cmd/cryptdecode/cryptdecode.go
+++ b/cmd/cryptdecode/cryptdecode.go
@@ -38,7 +38,7 @@ use it like this
rclone cryptdecode --reverse encryptedremote: filename1 filename2
Another way to accomplish this is by using the ` + "`rclone backend encode` (or `decode`)" + `command.
-See the documentation on the ` + "`crypt`" + ` overlay for more info.
+See the documentation on the [crypt](/crypt/) overlay for more info.
`,
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(2, 11, command, args)
diff --git a/cmd/delete/delete.go b/cmd/delete/delete.go
index 19816cb3a18a2..a432dbe56fb71 100644
--- a/cmd/delete/delete.go
+++ b/cmd/delete/delete.go
@@ -25,16 +25,16 @@ var commandDefinition = &cobra.Command{
Short: `Remove the files in path.`,
// Warning! "|" will be replaced by backticks below
Long: strings.ReplaceAll(`
-Remove the files in path. Unlike |purge| it obeys include/exclude
-filters so can be used to selectively delete files.
+Remove the files in path. Unlike [purge](/commands/rclone_purge/) it
+obeys include/exclude filters so can be used to selectively delete files.
|rclone delete| only deletes files but leaves the directory structure
alone. If you want to delete a directory and all of its contents use
-the |purge| command.
+the [purge](/commands/rclone_purge/) command.
If you supply the |--rmdirs| flag, it will remove all empty directories along with it.
-You can also use the separate command |rmdir| or |rmdirs| to
-delete empty directories only.
+You can also use the separate command [rmdir](/commands/rclone_rmdir/) or
+[rmdirs](/commands/rclone_rmdirs/) to delete empty directories only.
For example, to delete all files bigger than 100 MiB, you may first want to
check what would be deleted (use either):
diff --git a/cmd/hashsum/hashsum.go b/cmd/hashsum/hashsum.go
index d2be8fe5f9604..64dfde4ce5685 100644
--- a/cmd/hashsum/hashsum.go
+++ b/cmd/hashsum/hashsum.go
@@ -93,6 +93,9 @@ not supported by the remote, no hash will be returned. With the
download flag, the file will be downloaded from the remote and
hashed locally enabling any hash for any remote.
+For the MD5 and SHA1 algorithms there are also dedicated commands,
+[md5sum](/commands/rclone_md5sum/) and [sha1sum](/commands/rclone_sha1sum/).
+
This command can also hash data received on standard input (stdin),
by not passing a remote:path, or by passing a hyphen as remote:path
when there is data to read (if not, the hypen will be treated literaly,
diff --git a/cmd/md5sum/md5sum.go b/cmd/md5sum/md5sum.go
index 4f85c8c930f95..665ae44673e93 100644
--- a/cmd/md5sum/md5sum.go
+++ b/cmd/md5sum/md5sum.go
@@ -28,6 +28,10 @@ not supported by the remote, no hash will be returned. With the
download flag, the file will be downloaded from the remote and
hashed locally enabling MD5 for any remote.
+For other algorithms, see the [hashsum](/commands/rclone_hashsum/)
+command. Running ` + "`rclone md5sum remote:path`" + ` is equivalent
+to running ` + "`rclone hashsum MD5 remote:path`" + `.
+
This command can also hash data received on standard input (stdin),
by not passing a remote:path, or by passing a hyphen as remote:path
when there is data to read (if not, the hypen will be treated literaly,
diff --git a/cmd/move/move.go b/cmd/move/move.go
index 20ca4620909ab..3d7f87167eee9 100644
--- a/cmd/move/move.go
+++ b/cmd/move/move.go
@@ -33,6 +33,9 @@ Moves the contents of the source directory to the destination
directory. Rclone will error if the source and destination overlap and
the remote does not support a server-side directory move operation.
+To move single files, use the [moveto](/commands/rclone_moveto/)
+command instead.
+
If no filters are in use and if possible this will server-side move
|source:path| into |dest:path|. After this |source:path| will no
longer exist.
@@ -43,7 +46,8 @@ move will be used, otherwise it will copy it (server-side if possible)
into |dest:path| then delete the original (if no errors on copy) in
|source:path|.
-If you want to delete empty source directories after move, use the --delete-empty-src-dirs flag.
+If you want to delete empty source directories after move, use the
+|--delete-empty-src-dirs| flag.
See the [--no-traverse](/docs/#no-traverse) option for controlling
whether rclone lists the destination directory or not. Supplying this
diff --git a/cmd/moveto/moveto.go b/cmd/moveto/moveto.go
index 8390371db3957..8d81bb3d39c3a 100644
--- a/cmd/moveto/moveto.go
+++ b/cmd/moveto/moveto.go
@@ -22,7 +22,7 @@ directory named dest:path.
This can be used to rename files or upload single files to other than
their existing name. If the source is a directory then it acts exactly
-like the move command.
+like the [move](/commands/rclone_move/) command.
So
diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go
index aea2501eb7a12..7f63c8b042a1f 100644
--- a/cmd/ncdu/ncdu.go
+++ b/cmd/ncdu/ncdu.go
@@ -69,6 +69,10 @@ but is useful as it stands.
Note that it might take some time to delete big files/directories. The
UI won't respond in the meantime since the deletion is done synchronously.
+
+For a non-interactive listing of the remote, see the
+[tree](/commands/rclone_tree/) command. To just get the total size of
+the remote you can also use the [size](/commands/rclone_size/) command.
`,
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(1, 1, command, args)
diff --git a/cmd/purge/purge.go b/cmd/purge/purge.go
index 137e3bd18a51a..335615cf96c2f 100644
--- a/cmd/purge/purge.go
+++ b/cmd/purge/purge.go
@@ -17,9 +17,10 @@ var commandDefinition = &cobra.Command{
Short: `Remove the path and all of its contents.`,
Long: `
Remove the path and all of its contents. Note that this does not obey
-include/exclude filters - everything will be removed. Use the ` + "`delete`" + `
-command if you want to selectively delete files. To delete empty directories only,
-use command ` + "`rmdir`" + ` or ` + "`rmdirs`" + `.
+include/exclude filters - everything will be removed. Use the
+[delete](/commands/rclone_delete/) command if you want to selectively
+delete files. To delete empty directories only, use command
+[rmdir](/commands/rclone_rmdir/) or [rmdirs](/commands/rclone_rmdirs/).
**Important**: Since this can cause data loss, test first with the
` + "`--dry-run` or the `--interactive`/`-i`" + ` flag.
diff --git a/cmd/rmdir/rmdir.go b/cmd/rmdir/rmdir.go
index 31b958a5bfae9..9b532ca4709b1 100644
--- a/cmd/rmdir/rmdir.go
+++ b/cmd/rmdir/rmdir.go
@@ -18,10 +18,10 @@ var commandDefinition = &cobra.Command{
Long: `
This removes empty directory given by path. Will not remove the path if it
has any objects in it, not even empty subdirectories. Use
-command ` + "`rmdirs`" + ` (or ` + "`delete`" + ` with option ` + "`--rmdirs`" + `)
-to do that.
+command [rmdirs](/commands/rclone_rmdirs/) (or [delete](/commands/rclone_delete/)
+with option ` + "`--rmdirs`" + `) to do that.
-To delete a path and any objects in it, use ` + "`purge`" + ` command.
+To delete a path and any objects in it, use [purge](/commands/rclone_purge/) command.
`,
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(1, 1, command, args)
diff --git a/cmd/rmdirs/rmdirs.go b/cmd/rmdirs/rmdirs.go
index 4631dee85b8eb..41788c5271dbe 100644
--- a/cmd/rmdirs/rmdirs.go
+++ b/cmd/rmdirs/rmdirs.go
@@ -26,15 +26,16 @@ that only contain empty directories), that it finds under the path.
The root path itself will also be removed if it is empty, unless
you supply the ` + "`--leave-root`" + ` flag.
-Use command ` + "`rmdir`" + ` to delete just the empty directory
-given by path, not recurse.
+Use command [rmdir](/commands/rclone_rmdir/) to delete just the empty
+directory given by path, not recurse.
This is useful for tidying up remotes that rclone has left a lot of
-empty directories in. For example the ` + "`delete`" + ` command will
-delete files but leave the directory structure (unless used with
-option ` + "`--rmdirs`" + `).
+empty directories in. For example the [delete](/commands/rclone_delete/)
+command will delete files but leave the directory structure (unless
+used with option ` + "`--rmdirs`" + `).
-To delete a path and any objects in it, use ` + "`purge`" + ` command.
+To delete a path and any objects in it, use [purge](/commands/rclone_purge/)
+command.
`,
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(1, 1, command, args)
diff --git a/cmd/serve/webdav/webdav.go b/cmd/serve/webdav/webdav.go
index a618c6c06ab68..89a76b2308b34 100644
--- a/cmd/serve/webdav/webdav.go
+++ b/cmd/serve/webdav/webdav.go
@@ -59,9 +59,8 @@ based on the ModTime and Size of the object.
If this flag is set to "auto" then rclone will choose the first
supported hash on the backend or you can use a named hash such as
-"MD5" or "SHA-1".
-
-Use "rclone hashsum" to see the full list.
+"MD5" or "SHA-1". Use the [hashsum](/commands/rclone_hashsum/) command
+to see the full list.
` + httplib.Help + vfs.Help + proxy.Help,
RunE: func(command *cobra.Command, args []string) error {
diff --git a/cmd/sha1sum/sha1sum.go b/cmd/sha1sum/sha1sum.go
index 78a70de51913c..1c3115a8b2712 100644
--- a/cmd/sha1sum/sha1sum.go
+++ b/cmd/sha1sum/sha1sum.go
@@ -28,6 +28,10 @@ not supported by the remote, no hash will be returned. With the
download flag, the file will be downloaded from the remote and
hashed locally enabling SHA-1 for any remote.
+For other algorithms, see the [hashsum](/commands/rclone_hashsum/)
+command. Running ` + "`rclone sha1sum remote:path`" + ` is equivalent
+to running ` + "`rclone hashsum SHA1 remote:path`" + `.
+
This command can also hash data received on standard input (stdin),
by not passing a remote:path, or by passing a hyphen as remote:path
when there is data to read (if not, the hypen will be treated literaly,
diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go
index 2bb085073f309..c0c1885ba5d5b 100644
--- a/cmd/sync/sync.go
+++ b/cmd/sync/sync.go
@@ -28,7 +28,9 @@ Sync the source to the destination, changing the destination
only. Doesn't transfer files that are identical on source and
destination, testing by size and modification time or MD5SUM.
Destination is updated to match source, including deleting files
-if necessary (except duplicate objects, see below).
+if necessary (except duplicate objects, see below). If you don't
+want to delete files from destination, use the
+[copy](/commands/rclone_copy/) command instead.
**Important**: Since this can cause data loss, test first with the
` + "`--dry-run` or the `--interactive`/`-i`" + ` flag.
@@ -42,7 +44,7 @@ those providers that support it) are also not yet handled.
It is always the contents of the directory that is synced, not the
directory itself. So when source:path is a directory, it's the contents of
source:path that are copied, not the directory name and contents. See
-extended explanation in the ` + "`" + `copy` + "`" + ` command above if unsure.
+extended explanation in the [copy](/commands/rclone_copy/) command if unsure.
If dest:path doesn't exist, it is created and the source:path contents
go there.
diff --git a/cmd/tree/tree.go b/cmd/tree/tree.go
index 4986c4d701cc7..4bd8994626556 100644
--- a/cmd/tree/tree.go
+++ b/cmd/tree/tree.go
@@ -84,11 +84,15 @@ For example
1 directories, 5 files
You can use any of the filtering options with the tree command (e.g.
---include and --exclude). You can also use --fast-list.
+` + "`--include` and `--exclude`" + `. You can also use ` + "`--fast-list`" + `.
The tree command has many options for controlling the listing which
-are compatible with the tree command. Note that not all of them have
+are compatible with the tree command, for example you can include file
+sizes with ` + "`--size`" + `. Note that not all of them have
short options as they conflict with rclone's short options.
+
+For a more interactive navigation of the remote see the
+[ncdu](/commands/rclone_ncdu/) command.
`,
RunE: func(command *cobra.Command, args []string) error {
cmd.CheckArgs(1, 1, command, args)
diff --git a/docs/content/rc.md b/docs/content/rc.md
index be642bd6294a5..8ea6b77d6a655 100644
--- a/docs/content/rc.md
+++ b/docs/content/rc.md
@@ -8,10 +8,10 @@ description: "Remote controlling rclone with its API"
If rclone is run with the `--rc` flag then it starts an HTTP server
which can be used to remote control rclone using its API.
-You can either use the [rclone rc](#api-rc) command to access the API
+You can either use the [rc](#api-rc) command to access the API
or [use HTTP directly](#api-http).
-If you just want to run a remote control then see the [rcd command](/commands/rclone_rcd/).
+If you just want to run a remote control then see the [rcd](/commands/rclone_rcd/) command.
## Supported parameters
@@ -544,8 +544,7 @@ This takes the following parameters:
- state - state to restart with - used with continue
- result - result to restart with - used with continue
-
-See the [config create command](/commands/rclone_config_create/) command for more information on the above.
+See the [config create](/commands/rclone_config_create/) command for more information on the above.
**Authentication is required for this call.**
@@ -555,7 +554,7 @@ Parameters:
- name - name of remote to delete
-See the [config delete command](/commands/rclone_config_delete/) command for more information on the above.
+See the [config delete](/commands/rclone_config_delete/) command for more information on the above.
**Authentication is required for this call.**
@@ -566,7 +565,7 @@ Returns a JSON object:
Where keys are remote names and values are the config parameters.
-See the [config dump command](/commands/rclone_config_dump/) command for more information on the above.
+See the [config dump](/commands/rclone_config_dump/) command for more information on the above.
**Authentication is required for this call.**
@@ -576,7 +575,7 @@ Parameters:
- name - name of remote to get
-See the [config dump command](/commands/rclone_config_dump/) command for more information on the above.
+See the [config dump](/commands/rclone_config_dump/) command for more information on the above.
**Authentication is required for this call.**
@@ -585,7 +584,7 @@ See the [config dump command](/commands/rclone_config_dump/) command for more in
Returns
- remotes - array of remote names
-See the [listremotes command](/commands/rclone_listremotes/) command for more information on the above.
+See the [listremotes](/commands/rclone_listremotes/) command for more information on the above.
**Authentication is required for this call.**
@@ -596,8 +595,7 @@ This takes the following parameters:
- name - name of remote
- parameters - a map of \{ "key": "value" \} pairs
-
-See the [config password command](/commands/rclone_config_password/) command for more information on the above.
+See the [config password](/commands/rclone_config_password/) command for more information on the above.
**Authentication is required for this call.**
@@ -606,7 +604,7 @@ See the [config password command](/commands/rclone_config_password/) command for
Returns a JSON object:
- providers - array of objects
-See the [config providers command](/commands/rclone_config_providers/) command for more information on the above.
+See the [config providers](/commands/rclone_config_providers/) command for more information on the above.
**Authentication is required for this call.**
@@ -625,8 +623,7 @@ This takes the following parameters:
- state - state to restart with - used with continue
- result - result to restart with - used with continue
-
-See the [config update command](/commands/rclone_config_update/) command for more information on the above.
+See the [config update](/commands/rclone_config_update/) command for more information on the above.
**Authentication is required for this call.**
@@ -1072,7 +1069,7 @@ This takes the following parameters:
The result is as returned from rclone about --json
-See the [about command](/commands/rclone_size/) command for more information on the above.
+See the [about](/commands/rclone_size/) command for more information on the above.
**Authentication is required for this call.**
@@ -1082,7 +1079,7 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
-See the [cleanup command](/commands/rclone_cleanup/) command for more information on the above.
+See the [cleanup](/commands/rclone_cleanup/) command for more information on the above.
**Authentication is required for this call.**
@@ -1104,8 +1101,9 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
- url - string, URL to read from
- - autoFilename - boolean, set to true to retrieve destination file name from url
-See the [copyurl command](/commands/rclone_copyurl/) command for more information on the above.
+- autoFilename - boolean, set to true to retrieve destination file name from url
+
+See the [copyurl](/commands/rclone_copyurl/) command for more information on the above.
**Authentication is required for this call.**
@@ -1115,7 +1113,7 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
-See the [delete command](/commands/rclone_delete/) command for more information on the above.
+See the [delete](/commands/rclone_delete/) command for more information on the above.
**Authentication is required for this call.**
@@ -1126,7 +1124,7 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
-See the [deletefile command](/commands/rclone_deletefile/) command for more information on the above.
+See the [deletefile](/commands/rclone_deletefile/) command for more information on the above.
**Authentication is required for this call.**
@@ -1209,7 +1207,7 @@ Returns:
- list
- This is an array of objects as described in the lsjson command
-See the [lsjson command](/commands/rclone_lsjson/) for more information on the above and examples.
+See the [lsjson](/commands/rclone_lsjson/) for more information on the above and examples.
**Authentication is required for this call.**
@@ -1220,7 +1218,7 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
-See the [mkdir command](/commands/rclone_mkdir/) command for more information on the above.
+See the [mkdir](/commands/rclone_mkdir/) command for more information on the above.
**Authentication is required for this call.**
@@ -1248,7 +1246,7 @@ Returns:
- url - URL of the resource
-See the [link command](/commands/rclone_link/) command for more information on the above.
+See the [link](/commands/rclone_link/) command for more information on the above.
**Authentication is required for this call.**
@@ -1259,7 +1257,7 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
-See the [purge command](/commands/rclone_purge/) command for more information on the above.
+See the [purge](/commands/rclone_purge/) command for more information on the above.
**Authentication is required for this call.**
@@ -1270,7 +1268,7 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
-See the [rmdir command](/commands/rclone_rmdir/) command for more information on the above.
+See the [rmdir](/commands/rclone_rmdir/) command for more information on the above.
**Authentication is required for this call.**
@@ -1281,7 +1279,8 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
- leaveRoot - boolean, set to true not to delete the root
-See the [rmdirs command](/commands/rclone_rmdirs/) command for more information on the above.
+
+See the [rmdirs](/commands/rclone_rmdirs/) command for more information on the above.
**Authentication is required for this call.**
@@ -1297,7 +1296,7 @@ Returns:
- bytes - number of bytes in those files
- sizeless - number of files with unknown size, included in count but not accounted for in bytes
-See the [size command](/commands/rclone_size/) command for more information on the above.
+See the [size](/commands/rclone_size/) command for more information on the above.
**Authentication is required for this call.**
@@ -1317,7 +1316,7 @@ The result is
Note that if you are only interested in files then it is much more
efficient to set the filesOnly flag in the options.
-See the [lsjson command](/commands/rclone_lsjson/) for more information on the above and examples.
+See the [lsjson](/commands/rclone_lsjson/) for more information on the above and examples.
**Authentication is required for this call.**
@@ -1328,7 +1327,8 @@ This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
- each part in body represents a file to be uploaded
-See the [uploadfile command](/commands/rclone_uploadfile/) command for more information on the above.
+
+See the [uploadfile](/commands/rclone_uploadfile/) command for more information on the above.
**Authentication is required for this call.**
@@ -1542,8 +1542,7 @@ This takes the following parameters:
- dstFs - a remote name string e.g. "drive:dst" for the destination
- createEmptySrcDirs - create empty src directories on destination if set
-
-See the [copy command](/commands/rclone_copy/) command for more information on the above.
+See the [copy](/commands/rclone_copy/) command for more information on the above.
**Authentication is required for this call.**
@@ -1556,8 +1555,7 @@ This takes the following parameters:
- createEmptySrcDirs - create empty src directories on destination if set
- deleteEmptySrcDirs - delete empty src directories if set
-
-See the [move command](/commands/rclone_move/) command for more information on the above.
+See the [move](/commands/rclone_move/) command for more information on the above.
**Authentication is required for this call.**
@@ -1570,7 +1568,7 @@ This takes the following parameters:
- createEmptySrcDirs - create empty src directories on destination if set
-See the [sync command](/commands/rclone_sync/) command for more information on the above.
+See the [sync](/commands/rclone_sync/) command for more information on the above.
**Authentication is required for this call.**
diff --git a/fs/config/rc.go b/fs/config/rc.go
index db5d48ccaea32..78f6d4866d632 100644
--- a/fs/config/rc.go
+++ b/fs/config/rc.go
@@ -20,7 +20,7 @@ Returns a JSON object:
Where keys are remote names and values are the config parameters.
-See the [config dump command](/commands/rclone_config_dump/) command for more information on the above.
+See the [config dump](/commands/rclone_config_dump/) command for more information on the above.
`,
})
}
@@ -41,7 +41,7 @@ Parameters:
- name - name of remote to get
-See the [config dump command](/commands/rclone_config_dump/) command for more information on the above.
+See the [config dump](/commands/rclone_config_dump/) command for more information on the above.
`,
})
}
@@ -65,7 +65,7 @@ func init() {
Returns
- remotes - array of remote names
-See the [listremotes command](/commands/rclone_listremotes/) command for more information on the above.
+See the [listremotes](/commands/rclone_listremotes/) command for more information on the above.
`,
})
}
@@ -89,7 +89,7 @@ func init() {
Returns a JSON object:
- providers - array of objects
-See the [config providers command](/commands/rclone_config_providers/) command for more information on the above.
+See the [config providers](/commands/rclone_config_providers/) command for more information on the above.
`,
})
}
@@ -133,7 +133,7 @@ func init() {
- parameters - a map of \{ "key": "value" \} pairs
` + extraHelp + `
-See the [config ` + name + ` command](/commands/rclone_config_` + name + `/) command for more information on the above.`,
+See the [config ` + name + `](/commands/rclone_config_` + name + `/) command for more information on the above.`,
})
}
}
@@ -203,7 +203,7 @@ Parameters:
- name - name of remote to delete
-See the [config delete command](/commands/rclone_config_delete/) command for more information on the above.
+See the [config delete](/commands/rclone_config_delete/) command for more information on the above.
`,
})
}
diff --git a/fs/operations/rc.go b/fs/operations/rc.go
index 848b56ca10a22..b8c59ce2d8d87 100644
--- a/fs/operations/rc.go
+++ b/fs/operations/rc.go
@@ -41,7 +41,7 @@ Returns:
- list
- This is an array of objects as described in the lsjson command
-See the [lsjson command](/commands/rclone_lsjson/) for more information on the above and examples.
+See the [lsjson](/commands/rclone_lsjson/) command for more information on the above and examples.
`,
})
}
@@ -90,7 +90,7 @@ The result is
Note that if you are only interested in files then it is much more
efficient to set the filesOnly flag in the options.
-See the [lsjson command](/commands/rclone_lsjson/) for more information on the above and examples.
+See the [lsjson](/commands/rclone_lsjson/) command for more information on the above and examples.
`,
})
}
@@ -127,7 +127,7 @@ func init() {
The result is as returned from rclone about --json
-See the [about command](/commands/rclone_size/) command for more information on the above.
+See the [about](/commands/rclone_about/) command for more information on the above.
`,
})
}
@@ -202,11 +202,11 @@ func init() {
{name: "mkdir", title: "Make a destination directory or container"},
{name: "rmdir", title: "Remove an empty directory or container"},
{name: "purge", title: "Remove a directory or container and all of its contents"},
- {name: "rmdirs", title: "Remove all the empty directories in the path", help: "- leaveRoot - boolean, set to true not to delete the root"},
+ {name: "rmdirs", title: "Remove all the empty directories in the path", help: "- leaveRoot - boolean, set to true not to delete the root\n"},
{name: "delete", title: "Remove files in the path", noRemote: true},
{name: "deletefile", title: "Remove the single file pointed to"},
- {name: "copyurl", title: "Copy the URL to the object", help: "- url - string, URL to read from\n - autoFilename - boolean, set to true to retrieve destination file name from url"},
- {name: "uploadfile", title: "Upload file using multiform/form-data", help: "- each part in body represents a file to be uploaded", needsRequest: true},
+ {name: "copyurl", title: "Copy the URL to the object", help: "- url - string, URL to read from\n - autoFilename - boolean, set to true to retrieve destination file name from url\n"},
+ {name: "uploadfile", title: "Upload file using multiform/form-data", help: "- each part in body represents a file to be uploaded\n", needsRequest: true},
{name: "cleanup", title: "Remove trashed files in the remote or path", noRemote: true},
} {
op := op
@@ -226,7 +226,7 @@ func init() {
- fs - a remote name string e.g. "drive:"
` + remote + op.help + `
-See the [` + op.name + ` command](/commands/rclone_` + op.name + `/) command for more information on the above.
+See the [` + op.name + `](/commands/rclone_` + op.name + `/) command for more information on the above.
`,
})
}
@@ -334,7 +334,7 @@ Returns:
- count - number of files
- bytes - number of bytes in those files
-See the [size command](/commands/rclone_size/) command for more information on the above.
+See the [size](/commands/rclone_size/) command for more information on the above.
`,
})
}
@@ -373,7 +373,7 @@ Returns:
- url - URL of the resource
-See the [link command](/commands/rclone_link/) command for more information on the above.
+See the [link](/commands/rclone_link/) command for more information on the above.
`,
})
}
diff --git a/fs/sync/rc.go b/fs/sync/rc.go
index 7dee9dea2dac7..42ca315f6bf5b 100644
--- a/fs/sync/rc.go
+++ b/fs/sync/rc.go
@@ -27,7 +27,7 @@ func init() {
- createEmptySrcDirs - create empty src directories on destination if set
` + moveHelp + `
-See the [` + name + ` command](/commands/rclone_` + name + `/) command for more information on the above.`,
+See the [` + name + `](/commands/rclone_` + name + `/) command for more information on the above.`,
})
}
}
From 4f0eae366f9ea39d0f5695bba2e5df0002cb26ba Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 15:52:23 +0200
Subject: [PATCH 098/560] docs/ncdu: fix inconsistency in key map help text
Ctrl+L was listed as ^L, while Ctrl+c was listed as c-C. Changed the latter to ^c.
---
cmd/ncdu/ncdu.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go
index 7f63c8b042a1f..c005a94cfedbe 100644
--- a/cmd/ncdu/ncdu.go
+++ b/cmd/ncdu/ncdu.go
@@ -107,7 +107,7 @@ func helpText() (tr []string) {
" Y display current path",
" ^L refresh screen",
" ? to toggle help on and off",
- " q/ESC/c-C to quit",
+ " q/ESC/^c to quit",
}...)
return
}
From ee87e919c526e948c2d99ff0c60abbb73ed94780 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 16:06:04 +0200
Subject: [PATCH 099/560] docs/ncdu: note that refresh screen shortcut will fix
screen corruption
---
cmd/ncdu/ncdu.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go
index c005a94cfedbe..abb748b6d5276 100644
--- a/cmd/ncdu/ncdu.go
+++ b/cmd/ncdu/ncdu.go
@@ -105,7 +105,7 @@ func helpText() (tr []string) {
}
tr = append(tr, []string{
" Y display current path",
- " ^L refresh screen",
+ " ^L refresh screen (fix screen corruption)",
" ? to toggle help on and off",
" q/ESC/^c to quit",
}...)
From bc70a95fca06670c4e7065d621463417698186ae Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 18:59:54 +0200
Subject: [PATCH 100/560] docs: consistent capitalization of WebDAV, DLNA, HTTP
---
README.md | 2 +-
backend/http/http.go | 4 ++--
backend/webdav/webdav.go | 4 ++--
cmd/serve/http/data/data.go | 6 +++---
cmd/serve/httplib/httplib.go | 8 ++++----
cmd/serve/webdav/webdav.go | 12 ++++++------
docs/content/_index.md | 2 +-
docs/content/faq.md | 2 +-
docs/content/filtering.md | 2 +-
docs/content/http.md | 2 +-
docs/content/onedrive.md | 2 +-
docs/content/webdav.md | 4 ++--
12 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/README.md b/README.md
index 7a955d31f5162..ba85e16bdd11b 100644
--- a/README.md
+++ b/README.md
@@ -111,7 +111,7 @@ These backends adapt or modify other storage providers
* Optional encryption ([Crypt](https://rclone.org/crypt/))
* Optional FUSE mount ([rclone mount](https://rclone.org/commands/rclone_mount/))
* Multi-threaded downloads to local disk
- * Can [serve](https://rclone.org/commands/rclone_serve/) local or remote files over HTTP/WebDav/FTP/SFTP/dlna
+ * Can [serve](https://rclone.org/commands/rclone_serve/) local or remote files over HTTP/WebDAV/FTP/SFTP/DLNA
## Installation & documentation
diff --git a/backend/http/http.go b/backend/http/http.go
index e2a993a448aca..bd53da943c87a 100644
--- a/backend/http/http.go
+++ b/backend/http/http.go
@@ -35,11 +35,11 @@ var (
func init() {
fsi := &fs.RegInfo{
Name: "http",
- Description: "http Connection",
+ Description: "HTTP Connection",
NewFs: NewFs,
Options: []fs.Option{{
Name: "url",
- Help: "URL of http host to connect to.\n\nE.g. \"https://example.com\", or \"https://user:pass@example.com\" to use a username and password.",
+ Help: "URL of HTTP host to connect to.\n\nE.g. \"https://example.com\", or \"https://user:pass@example.com\" to use a username and password.",
Required: true,
}, {
Name: "headers",
diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go
index 7016b289cd042..d663ac0bfdd4d 100644
--- a/backend/webdav/webdav.go
+++ b/backend/webdav/webdav.go
@@ -66,7 +66,7 @@ func init() {
fs.Register(&fs.RegInfo{
Name: "webdav",
- Description: "Webdav",
+ Description: "WebDAV",
NewFs: NewFs,
Options: []fs.Option{{
Name: "url",
@@ -74,7 +74,7 @@ func init() {
Required: true,
}, {
Name: "vendor",
- Help: "Name of the Webdav site/service/software you are using.",
+ Help: "Name of the WebDAV site/service/software you are using.",
Examples: []fs.OptionExample{{
Value: "nextcloud",
Help: "Nextcloud",
diff --git a/cmd/serve/http/data/data.go b/cmd/serve/http/data/data.go
index f413c05526aba..de8362100fd7d 100644
--- a/cmd/serve/http/data/data.go
+++ b/cmd/serve/http/data/data.go
@@ -19,8 +19,8 @@ import (
var Help = `
#### Template
---template allows a user to specify a custom markup template for http
-and webdav serve functions. The server exports the following markup
+--template allows a user to specify a custom markup template for HTTP
+and WebDAV serve functions. The server exports the following markup
to be used within the template to server pages:
| Parameter | Description |
@@ -58,7 +58,7 @@ func AfterEpoch(t time.Time) bool {
return t.After(time.Time{})
}
-// GetTemplate returns the HTML template for serving directories via HTTP/Webdav
+// GetTemplate returns the HTML template for serving directories via HTTP/WebDAV
func GetTemplate(tmpl string) (tpl *template.Template, err error) {
var templateString string
if tmpl == "" {
diff --git a/cmd/serve/httplib/httplib.go b/cmd/serve/httplib/httplib.go
index ae6583af9b697..4e3efe7d02bac 100644
--- a/cmd/serve/httplib/httplib.go
+++ b/cmd/serve/httplib/httplib.go
@@ -53,8 +53,8 @@ inserts leading and trailing "/" on --baseurl, so --baseurl "rclone",
--baseurl "/rclone" and --baseurl "/rclone/" are all treated
identically.
---template allows a user to specify a custom markup template for http
-and webdav serve functions. The server exports the following markup
+--template allows a user to specify a custom markup template for HTTP
+and WebDAV serve functions. The server exports the following markup
to be used within the template to server pages:
| Parameter | Description |
@@ -99,8 +99,8 @@ Use --realm to set the authentication realm.
#### SSL/TLS
-By default this will serve over http. If you want you can serve over
-https. You will need to supply the --cert and --key flags. If you
+By default this will serve over HTTP. If you want you can serve over
+HTTPS. You will need to supply the --cert and --key flags. If you
wish to do client side certificate validation then you will need to
supply --client-ca also.
diff --git a/cmd/serve/webdav/webdav.go b/cmd/serve/webdav/webdav.go
index 89a76b2308b34..4d152a433238b 100644
--- a/cmd/serve/webdav/webdav.go
+++ b/cmd/serve/webdav/webdav.go
@@ -43,14 +43,14 @@ func init() {
// Command definition for cobra
var Command = &cobra.Command{
Use: "webdav remote:path",
- Short: `Serve remote:path over webdav.`,
+ Short: `Serve remote:path over WebDAV.`,
Long: `
-rclone serve webdav implements a basic webdav server to serve the
-remote over HTTP via the webdav protocol. This can be viewed with a
-webdav client, through a web browser, or you can make a remote of
-type webdav to read and write it.
+rclone serve webdav implements a basic WebDAV server to serve the
+remote over HTTP via the WebDAV protocol. This can be viewed with a
+WebDAV client, through a web browser, or you can make a remote of
+type WebDAV to read and write it.
-### Webdav options
+### WebDAV options
#### --etag-hash
diff --git a/docs/content/_index.md b/docs/content/_index.md
index c7a213bfa3970..42c8b48666586 100644
--- a/docs/content/_index.md
+++ b/docs/content/_index.md
@@ -95,7 +95,7 @@ Rclone helps you:
- [Move](/commands/rclone_move/) files to cloud storage deleting the local after verification
- [Check](/commands/rclone_check/) hashes and for missing/extra files
- [Mount](/commands/rclone_mount/) your cloud storage as a network disk
-- [Serve](/commands/rclone_serve/) local or remote files over [HTTP](/commands/rclone_serve_http/)/[WebDav](/commands/rclone_serve_webdav/)/[FTP](/commands/rclone_serve_ftp/)/[SFTP](/commands/rclone_serve_sftp/)/[dlna](/commands/rclone_serve_dlna/)
+- [Serve](/commands/rclone_serve/) local or remote files over [HTTP](/commands/rclone_serve_http/)/[WebDav](/commands/rclone_serve_webdav/)/[FTP](/commands/rclone_serve_ftp/)/[SFTP](/commands/rclone_serve_sftp/)/[DLNA](/commands/rclone_serve_dlna/)
- Experimental [Web based GUI](/gui/)
## Supported providers {#providers}
diff --git a/docs/content/faq.md b/docs/content/faq.md
index ecd2408829e5c..00ed0e06d3950 100644
--- a/docs/content/faq.md
+++ b/docs/content/faq.md
@@ -118,7 +118,7 @@ e.g.
export no_proxy=localhost,127.0.0.0/8,my.host.name
export NO_PROXY=$no_proxy
-Note that the ftp backend does not support `ftp_proxy` yet.
+Note that the FTP backend does not support `ftp_proxy` yet.
### Rclone gives x509: failed to load system roots and no roots provided error ###
diff --git a/docs/content/filtering.md b/docs/content/filtering.md
index de191ddc22ca4..8fc3bf109978d 100644
--- a/docs/content/filtering.md
+++ b/docs/content/filtering.md
@@ -274,7 +274,7 @@ every path against the supplied regular expression(s).
Directory recursion optimisation occurs if either:
* A source remote does not support the rclone `ListR` primitive. local,
-sftp, Microsoft OneDrive and WebDav do not support `ListR`. Google
+sftp, Microsoft OneDrive and WebDAV do not support `ListR`. Google
Drive and most bucket type storage do. [Full list](https://rclone.org/overview/#optional-features)
* On other remotes (those that support `ListR`), if the rclone command is not naturally recursive, and
diff --git a/docs/content/http.md b/docs/content/http.md
index 501cd5a22ade4..9affbf3584705 100644
--- a/docs/content/http.md
+++ b/docs/content/http.md
@@ -52,7 +52,7 @@ name> remote
Type of storage to configure.
Choose a number from below, or type in your own value
[snip]
-XX / http Connection
+XX / HTTP Connection
\ "http"
[snip]
Storage> http
diff --git a/docs/content/onedrive.md b/docs/content/onedrive.md
index f25bbe513ca22..36f3c89fbe07c 100644
--- a/docs/content/onedrive.md
+++ b/docs/content/onedrive.md
@@ -670,7 +670,7 @@ Description: Using application 'rclone' is currently not supported for your orga
This means that rclone can't use the OneDrive for Business API with your account. You can't do much about it, maybe write an email to your admins.
-However, there are other ways to interact with your OneDrive account. Have a look at the webdav backend: https://rclone.org/webdav/#sharepoint
+However, there are other ways to interact with your OneDrive account. Have a look at the WebDAV backend: https://rclone.org/webdav/#sharepoint
### invalid\_grant (AADSTS50076) ####
diff --git a/docs/content/webdav.md b/docs/content/webdav.md
index c5612fead8d2f..766a5cc8cd28b 100644
--- a/docs/content/webdav.md
+++ b/docs/content/webdav.md
@@ -31,7 +31,7 @@ name> remote
Type of storage to configure.
Choose a number from below, or type in your own value
[snip]
-XX / Webdav
+XX / WebDAV
\ "webdav"
[snip]
Storage> webdav
@@ -40,7 +40,7 @@ Choose a number from below, or type in your own value
1 / Connect to example.com
\ "https://example.com"
url> https://example.com/remote.php/webdav/
-Name of the Webdav site/service/software you are using
+Name of the WebDAV site/service/software you are using
Choose a number from below, or type in your own value
1 / Nextcloud
\ "nextcloud"
From 70d9d75801cd3aa59a4c856fcff06dce31a05544 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 19:18:14 +0200
Subject: [PATCH 101/560] docs: improve serve command descriptions
---
cmd/serve/dlna/dlna.go | 18 ++++++++++--------
cmd/serve/ftp/ftp.go | 6 +++---
cmd/serve/http/http.go | 6 +++---
cmd/serve/restic/restic.go | 4 ++--
cmd/serve/serve.go | 4 ++--
cmd/serve/sftp/sftp.go | 5 ++---
cmd/serve/webdav/webdav.go | 8 +++-----
7 files changed, 25 insertions(+), 26 deletions(-)
diff --git a/cmd/serve/dlna/dlna.go b/cmd/serve/dlna/dlna.go
index a6b71bf24fa44..0c9d2fbcaab51 100644
--- a/cmd/serve/dlna/dlna.go
+++ b/cmd/serve/dlna/dlna.go
@@ -34,14 +34,16 @@ func init() {
var Command = &cobra.Command{
Use: "dlna remote:path",
Short: `Serve remote:path over DLNA`,
- Long: `rclone serve dlna is a DLNA media server for media stored in an rclone remote. Many
-devices, such as the Xbox and PlayStation, can automatically discover this server in the LAN
-and play audio/video from it. VLC is also supported. Service discovery uses UDP multicast
-packets (SSDP) and will thus only work on LANs.
-
-Rclone will list all files present in the remote, without filtering based on media formats or
-file extensions. Additionally, there is no media transcoding support. This means that some
-players might show files that they are not able to play back correctly.
+ Long: `Run a DLNA media server for media stored in an rclone remote. Many
+devices, such as the Xbox and PlayStation, can automatically discover
+this server in the LAN and play audio/video from it. VLC is also
+supported. Service discovery uses UDP multicast packets (SSDP) and
+will thus only work on LANs.
+
+Rclone will list all files present in the remote, without filtering
+based on media formats or file extensions. Additionally, there is no
+media transcoding support. This means that some players might show
+files that they are not able to play back correctly.
` + dlnaflags.Help + vfs.Help,
Run: func(command *cobra.Command, args []string) {
diff --git a/cmd/serve/ftp/ftp.go b/cmd/serve/ftp/ftp.go
index b8022599e02a9..f8ba817259cad 100644
--- a/cmd/serve/ftp/ftp.go
+++ b/cmd/serve/ftp/ftp.go
@@ -80,9 +80,9 @@ var Command = &cobra.Command{
Use: "ftp remote:path",
Short: `Serve remote:path over FTP.`,
Long: `
-rclone serve ftp implements a basic ftp server to serve the
-remote over FTP protocol. This can be viewed with a ftp client
-or you can make a remote of type ftp to read and write it.
+Run a basic FTP server to serve a remote over FTP protocol.
+This can be viewed with a FTP client or you can make a remote of
+type FTP to read and write it.
### Server options
diff --git a/cmd/serve/http/http.go b/cmd/serve/http/http.go
index 780aa9c915907..c809d6c33e3be 100644
--- a/cmd/serve/http/http.go
+++ b/cmd/serve/http/http.go
@@ -47,9 +47,9 @@ func init() {
var Command = &cobra.Command{
Use: "http remote:path",
Short: `Serve the remote over HTTP.`,
- Long: `rclone serve http implements a basic web server to serve the remote
-over HTTP. This can be viewed in a web browser or you can make a
-remote of type http read from it.
+ Long: `Run a basic web server to serve a remote over HTTP.
+This can be viewed in a web browser or you can make a remote of type
+http read from it.
You can use the filter flags (e.g. --include, --exclude) to control what
is served.
diff --git a/cmd/serve/restic/restic.go b/cmd/serve/restic/restic.go
index b1ac10393b345..65b60dc053d82 100644
--- a/cmd/serve/restic/restic.go
+++ b/cmd/serve/restic/restic.go
@@ -50,8 +50,8 @@ func init() {
var Command = &cobra.Command{
Use: "restic remote:path",
Short: `Serve the remote for restic's REST API.`,
- Long: `rclone serve restic implements restic's REST backend API
-over HTTP. This allows restic to use rclone as a data storage
+ Long: `Run a basic web server to serve a remove over restic's REST backend
+API over HTTP. This allows restic to use rclone as a data storage
mechanism for cloud providers that restic does not support directly.
[Restic](https://restic.net/) is a command-line program for doing
diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go
index 7bed14c7e6873..848cb24bb34db 100644
--- a/cmd/serve/serve.go
+++ b/cmd/serve/serve.go
@@ -41,8 +41,8 @@ func init() {
var Command = &cobra.Command{
Use: "serve [opts] ",
Short: `Serve a remote over a protocol.`,
- Long: `rclone serve is used to serve a remote over a given protocol. This
-command requires the use of a subcommand to specify the protocol, e.g.
+ Long: `Serve a remote over a given protocol. Requires the use of a
+subcommand to specify the protocol, e.g.
rclone serve http remote:
diff --git a/cmd/serve/sftp/sftp.go b/cmd/serve/sftp/sftp.go
index b1be36bb1630b..cecb757077ac6 100644
--- a/cmd/serve/sftp/sftp.go
+++ b/cmd/serve/sftp/sftp.go
@@ -62,9 +62,8 @@ func init() {
var Command = &cobra.Command{
Use: "sftp remote:path",
Short: `Serve the remote over SFTP.`,
- Long: `rclone serve sftp implements an SFTP server to serve the remote
-over SFTP. This can be used with an SFTP client or you can make a
-remote of type sftp to use with it.
+ Long: `Run a SFTP server to serve a remote over SFTP. This can be used
+with an SFTP client or you can make a remote of type sftp to use with it.
You can use the filter flags (e.g. --include, --exclude) to control what
is served.
diff --git a/cmd/serve/webdav/webdav.go b/cmd/serve/webdav/webdav.go
index 4d152a433238b..bfd0eda31935d 100644
--- a/cmd/serve/webdav/webdav.go
+++ b/cmd/serve/webdav/webdav.go
@@ -44,11 +44,9 @@ func init() {
var Command = &cobra.Command{
Use: "webdav remote:path",
Short: `Serve remote:path over WebDAV.`,
- Long: `
-rclone serve webdav implements a basic WebDAV server to serve the
-remote over HTTP via the WebDAV protocol. This can be viewed with a
-WebDAV client, through a web browser, or you can make a remote of
-type WebDAV to read and write it.
+ Long: `Run a basic WebDAV server to serve a remote over HTTP via the
+WebDAV protocol. This can be viewed with a WebDAV client, through a web
+browser, or you can make a remote of type WebDAV to read and write it.
### WebDAV options
From dbf1234edfbfbd63673562dfcaa0b566eed2fd18 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 19:29:02 +0200
Subject: [PATCH 102/560] docs: skip "Connection" suffix from FTP, SSH/SFTP and
HTTP backend names
---
backend/ftp/ftp.go | 2 +-
backend/http/http.go | 2 +-
backend/sftp/sftp.go | 2 +-
docs/content/ftp.md | 2 +-
docs/content/http.md | 2 +-
docs/content/s3.md | 4 ++--
docs/content/sftp.md | 2 +-
7 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/backend/ftp/ftp.go b/backend/ftp/ftp.go
index 92b84fe9c014c..c078f78b095f0 100644
--- a/backend/ftp/ftp.go
+++ b/backend/ftp/ftp.go
@@ -45,7 +45,7 @@ const (
func init() {
fs.Register(&fs.RegInfo{
Name: "ftp",
- Description: "FTP Connection",
+ Description: "FTP",
NewFs: NewFs,
Options: []fs.Option{{
Name: "host",
diff --git a/backend/http/http.go b/backend/http/http.go
index bd53da943c87a..3e6e83155426d 100644
--- a/backend/http/http.go
+++ b/backend/http/http.go
@@ -35,7 +35,7 @@ var (
func init() {
fsi := &fs.RegInfo{
Name: "http",
- Description: "HTTP Connection",
+ Description: "HTTP",
NewFs: NewFs,
Options: []fs.Option{{
Name: "url",
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index 5e9cbe18dfd95..74af5fa4104a6 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -57,7 +57,7 @@ var (
func init() {
fsi := &fs.RegInfo{
Name: "sftp",
- Description: "SSH/SFTP Connection",
+ Description: "SSH/SFTP",
NewFs: NewFs,
Options: []fs.Option{{
Name: "host",
diff --git a/docs/content/ftp.md b/docs/content/ftp.md
index 203e944bcba6d..f2d4a4229ede5 100644
--- a/docs/content/ftp.md
+++ b/docs/content/ftp.md
@@ -39,7 +39,7 @@ Type of storage to configure.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
[snip]
-XX / FTP Connection
+XX / FTP
\ "ftp"
[snip]
Storage> ftp
diff --git a/docs/content/http.md b/docs/content/http.md
index 9affbf3584705..10ef35098324c 100644
--- a/docs/content/http.md
+++ b/docs/content/http.md
@@ -52,7 +52,7 @@ name> remote
Type of storage to configure.
Choose a number from below, or type in your own value
[snip]
-XX / HTTP Connection
+XX / HTTP
\ "http"
[snip]
Storage> http
diff --git a/docs/content/s3.md b/docs/content/s3.md
index 15b26e7e2944c..fbd47c76327cd 100644
--- a/docs/content/s3.md
+++ b/docs/content/s3.md
@@ -2901,7 +2901,7 @@ r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
-e/n/d/r/c/s/q> q
+e/n/d/r/c/s/q> q
```
### IBM COS (S3)
@@ -2937,7 +2937,7 @@ Choose a number from below, or type in your own value
4 / Backblaze B2
\ "b2"
[snip]
- 23 / http Connection
+ 23 / HTTP
\ "http"
Storage> 3
```
diff --git a/docs/content/sftp.md b/docs/content/sftp.md
index a0fc7271cb92e..fa0f5ba054943 100644
--- a/docs/content/sftp.md
+++ b/docs/content/sftp.md
@@ -49,7 +49,7 @@ name> remote
Type of storage to configure.
Choose a number from below, or type in your own value
[snip]
-XX / SSH/SFTP Connection
+XX / SSH/SFTP
\ "sftp"
[snip]
Storage> sftp
From bf54c909c91e3aa338b379b99c3f6c55655ba930 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 19:43:16 +0200
Subject: [PATCH 103/560] docs: improved capitalization
---
docs/content/_index.md | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/docs/content/_index.md b/docs/content/_index.md
index 42c8b48666586..dcf51e23f8c9c 100644
--- a/docs/content/_index.md
+++ b/docs/content/_index.md
@@ -174,14 +174,14 @@ WebDAV or S3, that work out of the box.)
These backends adapt or modify other storage providers:
-{{< provider name="Alias: rename existing remotes" home="/alias/" config="/alias/" >}}
-{{< provider name="Cache: cache remotes (DEPRECATED)" home="/cache/" config="/cache/" >}}
-{{< provider name="Chunker: split large files" home="/chunker/" config="/chunker/" >}}
-{{< provider name="Combine: combine multiple remotes into a directory tree" home="/combine/" config="/combine/" >}}
-{{< provider name="Compress: compress files" home="/compress/" config="/compress/" >}}
-{{< provider name="Crypt: encrypt files" home="/crypt/" config="/crypt/" >}}
-{{< provider name="Hasher: hash files" home="/hasher/" config="/hasher/" >}}
-{{< provider name="Union: join multiple remotes to work together" home="/union/" config="/union/" >}}
+{{< provider name="Alias: Rename existing remotes" home="/alias/" config="/alias/" >}}
+{{< provider name="Cache: Cache remotes (DEPRECATED)" home="/cache/" config="/cache/" >}}
+{{< provider name="Chunker: Split large files" home="/chunker/" config="/chunker/" >}}
+{{< provider name="Combine: Combine multiple remotes into a directory tree" home="/combine/" config="/combine/" >}}
+{{< provider name="Compress: Compress files" home="/compress/" config="/compress/" >}}
+{{< provider name="Crypt: Encrypt files" home="/crypt/" config="/crypt/" >}}
+{{< provider name="Hasher: Hash files" home="/hasher/" config="/hasher/" >}}
+{{< provider name="Union: Join multiple remotes to work together" home="/union/" config="/union/" >}}
## Links
From 9f81b4df4f65e277c8d4363724a448bfc5608c8c Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 19:45:38 +0200
Subject: [PATCH 104/560] docs/lsjson: fix code block indentation
---
cmd/lsjson/lsjson.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cmd/lsjson/lsjson.go b/cmd/lsjson/lsjson.go
index 47bafa4e3b876..aa23c210d9479 100644
--- a/cmd/lsjson/lsjson.go
+++ b/cmd/lsjson/lsjson.go
@@ -41,7 +41,7 @@ var commandDefinition = &cobra.Command{
The output is an array of Items, where each Item looks like this
- {
+ {
"Hashes" : {
"SHA-1" : "f572d396fae9206628714fb2ce00f72e94f2258f",
"MD5" : "b1946ac92492d2347c6235b4d2611184",
@@ -59,7 +59,7 @@ The output is an array of Items, where each Item looks like this
"Path" : "full/path/goes/here/file.txt",
"Size" : 6,
"Tier" : "hot",
- }
+ }
If --hash is not specified the Hashes property won't be emitted. The
types of hash can be specified with the --hash-type parameter (which
From 53f831f40a7341cc3da199b0266ea573b6d94551 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sun, 19 Jun 2022 19:55:37 +0200
Subject: [PATCH 105/560] docs: add missing code section formatting to commands
and flags
---
cmd/cryptdecode/cryptdecode.go | 4 +--
cmd/dedupe/dedupe.go | 2 +-
cmd/genautocomplete/genautocomplete.go | 2 +-
cmd/listremotes/listremotes.go | 2 +-
cmd/lsd/lsd.go | 4 +--
cmd/lsf/lsf.go | 12 ++++----
cmd/lsjson/lsjson.go | 20 ++++++-------
cmd/obscure/obscure.go | 2 +-
cmd/rc/rc.go | 20 ++++++-------
cmd/rcat/rcat.go | 6 ++--
cmd/serve/http/data/data.go | 2 +-
cmd/serve/http/http.go | 6 ++--
cmd/serve/httplib/httplib.go | 40 +++++++++++++-------------
cmd/serve/restic/restic.go | 10 +++----
cmd/serve/sftp/sftp.go | 29 ++++++++++---------
lib/http/auth/auth.go | 8 +++---
lib/http/http.go | 30 +++++++++----------
17 files changed, 100 insertions(+), 99 deletions(-)
diff --git a/cmd/cryptdecode/cryptdecode.go b/cmd/cryptdecode/cryptdecode.go
index 27ab93df131db..2f938cbfa7a0a 100644
--- a/cmd/cryptdecode/cryptdecode.go
+++ b/cmd/cryptdecode/cryptdecode.go
@@ -29,7 +29,7 @@ var commandDefinition = &cobra.Command{
rclone cryptdecode returns unencrypted file names when provided with
a list of encrypted file names. List limit is 10 items.
-If you supply the --reverse flag, it will return encrypted file names.
+If you supply the ` + "`--reverse`" + ` flag, it will return encrypted file names.
use it like this
@@ -37,7 +37,7 @@ use it like this
rclone cryptdecode --reverse encryptedremote: filename1 filename2
-Another way to accomplish this is by using the ` + "`rclone backend encode` (or `decode`)" + `command.
+Another way to accomplish this is by using the ` + "`rclone backend encode` (or `decode`)" + ` command.
See the documentation on the [crypt](/crypt/) overlay for more info.
`,
Run: func(command *cobra.Command, args []string) {
diff --git a/cmd/dedupe/dedupe.go b/cmd/dedupe/dedupe.go
index 270634aede92f..b43c4c2795663 100644
--- a/cmd/dedupe/dedupe.go
+++ b/cmd/dedupe/dedupe.go
@@ -37,7 +37,7 @@ Opendrive) that can have duplicate file names. It can be run on wrapping backend
(e.g. crypt) if they wrap a backend which supports duplicate file
names.
-However if --by-hash is passed in then dedupe will find files with
+However if ` + "`--by-hash`" + ` is passed in then dedupe will find files with
duplicate hashes instead which will work on any backend which supports
at least one hash. This can be used to find files with duplicate
content. This is known as deduping by hash.
diff --git a/cmd/genautocomplete/genautocomplete.go b/cmd/genautocomplete/genautocomplete.go
index 760d17dc05779..5971135770d54 100644
--- a/cmd/genautocomplete/genautocomplete.go
+++ b/cmd/genautocomplete/genautocomplete.go
@@ -14,6 +14,6 @@ var completionDefinition = &cobra.Command{
Short: `Output completion script for a given shell.`,
Long: `
Generates a shell completion script for rclone.
-Run with --help to list the supported shells.
+Run with ` + "`--help`" + ` to list the supported shells.
`,
}
diff --git a/cmd/listremotes/listremotes.go b/cmd/listremotes/listremotes.go
index 9d1e7f0b8211f..908979a4fa34f 100644
--- a/cmd/listremotes/listremotes.go
+++ b/cmd/listremotes/listremotes.go
@@ -27,7 +27,7 @@ var commandDefinition = &cobra.Command{
Long: `
rclone listremotes lists all the available remotes from the config file.
-When uses with the -l flag it lists the types too.
+When used with the ` + "`--long`" + ` flag it lists the types too.
`,
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(0, 0, command, args)
diff --git a/cmd/lsd/lsd.go b/cmd/lsd/lsd.go
index 75db3bb7335b5..bd723daadb5aa 100644
--- a/cmd/lsd/lsd.go
+++ b/cmd/lsd/lsd.go
@@ -27,7 +27,7 @@ var commandDefinition = &cobra.Command{
Short: `List all directories/containers/buckets in the path.`,
Long: `
Lists the directories in the source path to standard output. Does not
-recurse by default. Use the -R flag to recurse.
+recurse by default. Use the ` + "`-R`" + ` flag to recurse.
This command lists the total size of the directory (if known, -1 if
not), the modification time (if known, the current time if not), the
@@ -45,7 +45,7 @@ Or
-1 2017-01-03 14:40:54 -1 2500files
-1 2017-07-08 14:39:28 -1 4000files
-If you just want the directory names use "rclone lsf --dirs-only".
+If you just want the directory names use ` + "`rclone lsf --dirs-only`" + `.
` + lshelp.Help,
Run: func(command *cobra.Command, args []string) {
diff --git a/cmd/lsf/lsf.go b/cmd/lsf/lsf.go
index a461a5c9cd961..812edf8eab748 100644
--- a/cmd/lsf/lsf.go
+++ b/cmd/lsf/lsf.go
@@ -59,7 +59,7 @@ Eg
ferejej3gux/
fubuwic
-Use the --format option to control what gets listed. By default this
+Use the ` + "`--format`" + ` option to control what gets listed. By default this
is just the path, but you can use these parameters to control the
output:
@@ -74,7 +74,7 @@ output:
T - tier of storage if known, e.g. "Hot" or "Cool"
So if you wanted the path, size and modification time, you would use
---format "pst", or maybe --format "tsp" to put the path last.
+` + "`--format \"pst\"`, or maybe `--format \"tsp\"`" + ` to put the path last.
Eg
@@ -86,7 +86,7 @@ Eg
2016-06-25 18:55:40;37600;fubuwic
If you specify "h" in the format you will get the MD5 hash by default,
-use the "--hash" flag to change which hash you want. Note that this
+use the ` + "`--hash`" + ` flag to change which hash you want. Note that this
can be returned as an empty string if it isn't available on the object
(and for directories), "ERROR" if there was an error reading it from
the object and "UNSUPPORTED" if that object does not support that hash
@@ -108,7 +108,7 @@ Eg
(Though "rclone md5sum ." is an easier way of typing this.)
By default the separator is ";" this can be changed with the
---separator flag. Note that separators aren't escaped in the path so
+` + "`--separator`" + ` flag. Note that separators aren't escaped in the path so
putting it last is a good strategy.
Eg
@@ -130,8 +130,8 @@ Eg
test.sh,449
"this file contains a comma, in the file name.txt",6
-Note that the --absolute parameter is useful for making lists of files
-to pass to an rclone copy with the --files-from-raw flag.
+Note that the ` + "`--absolute`" + ` parameter is useful for making lists of files
+to pass to an rclone copy with the ` + "`--files-from-raw`" + ` flag.
For example, to find all the files modified within one day and copy
those only (without traversing the whole directory structure):
diff --git a/cmd/lsjson/lsjson.go b/cmd/lsjson/lsjson.go
index aa23c210d9479..70b96a0f76513 100644
--- a/cmd/lsjson/lsjson.go
+++ b/cmd/lsjson/lsjson.go
@@ -61,27 +61,27 @@ The output is an array of Items, where each Item looks like this
"Tier" : "hot",
}
-If --hash is not specified the Hashes property won't be emitted. The
-types of hash can be specified with the --hash-type parameter (which
-may be repeated). If --hash-type is set then it implies --hash.
+If ` + "`--hash`" + ` is not specified the Hashes property won't be emitted. The
+types of hash can be specified with the ` + "`--hash-type`" + ` parameter (which
+may be repeated). If ` + "`--hash-type`" + ` is set then it implies ` + "`--hash`" + `.
-If --no-modtime is specified then ModTime will be blank. This can
+If ` + "`--no-modtime`" + ` is specified then ModTime will be blank. This can
speed things up on remotes where reading the ModTime takes an extra
request (e.g. s3, swift).
-If --no-mimetype is specified then MimeType will be blank. This can
+If ` + "`--no-mimetype`" + ` is specified then MimeType will be blank. This can
speed things up on remotes where reading the MimeType takes an extra
request (e.g. s3, swift).
-If --encrypted is not specified the Encrypted won't be emitted.
+If ` + "`--encrypted`" + ` is not specified the Encrypted won't be emitted.
-If --dirs-only is not specified files in addition to directories are
+If ` + "`--dirs-only`" + ` is not specified files in addition to directories are
returned
-If --files-only is not specified directories in addition to the files
+If ` + "`--files-only`" + ` is not specified directories in addition to the files
will be returned.
-if --stat is set then a single JSON blob will be returned about the
+if ` + "`--stat`" + ` is set then a single JSON blob will be returned about the
item pointed to. This will return an error if the item isn't found.
However on bucket based backends (like s3, gcs, b2, azureblob etc) if
the item isn't found it will return an empty directory as it isn't
@@ -90,7 +90,7 @@ possible to tell empty directories from missing directories there.
The Path field will only show folders below the remote path being listed.
If "remote:path" contains the file "subfolder/file.txt", the Path for "file.txt"
will be "subfolder/file.txt", not "remote:path/subfolder/file.txt".
-When used without --recursive the Path will always be the same as Name.
+When used without ` + "`--recursive`" + ` the Path will always be the same as Name.
If the directory is a bucket in a bucket-based backend, then
"IsBucket" will be set to true. This key won't be present unless it is
diff --git a/cmd/obscure/obscure.go b/cmd/obscure/obscure.go
index 71987079d30f5..d26f1d72b660b 100644
--- a/cmd/obscure/obscure.go
+++ b/cmd/obscure/obscure.go
@@ -33,7 +33,7 @@ This command can also accept a password through STDIN instead of an
argument by passing a hyphen as an argument. This will use the first
line of STDIN as the password not including the trailing newline.
-echo "secretpassword" | rclone obscure -
+ echo "secretpassword" | rclone obscure -
If there is no data on STDIN to read, rclone obscure will default to
obfuscating the hyphen itself.
diff --git a/cmd/rc/rc.go b/cmd/rc/rc.go
index fec0d5c403b61..6b123f97e3ffb 100644
--- a/cmd/rc/rc.go
+++ b/cmd/rc/rc.go
@@ -50,26 +50,26 @@ var commandDefinition = &cobra.Command{
Short: `Run a command against a running rclone.`,
Long: `
-This runs a command against a running rclone. Use the --url flag to
+This runs a command against a running rclone. Use the ` + "`--url`" + ` flag to
specify an non default URL to connect on. This can be either a
":port" which is taken to mean "http://localhost:port" or a
"host:port" which is taken to mean "http://host:port"
-A username and password can be passed in with --user and --pass.
+A username and password can be passed in with ` + "`--user`" + ` and ` + "`--pass`" + `.
-Note that --rc-addr, --rc-user, --rc-pass will be read also for --url,
---user, --pass.
+Note that ` + "`--rc-addr`, `--rc-user`, `--rc-pass`" + ` will be read also for
+` + "`--url`, `--user`, `--pass`" + `.
Arguments should be passed in as parameter=value.
The result will be returned as a JSON object by default.
-The --json parameter can be used to pass in a JSON blob as an input
+The ` + "`--json`" + ` parameter can be used to pass in a JSON blob as an input
instead of key=value arguments. This is the only way of passing in
more complicated values.
-The -o/--opt option can be used to set a key "opt" with key, value
-options in the form "-o key=value" or "-o key". It can be repeated as
+The ` + "`-o`/`--opt`" + ` option can be used to set a key "opt" with key, value
+options in the form ` + "`-o key=value` or `-o key`" + `. It can be repeated as
many times as required. This is useful for rc commands which take the
"opt" parameter which by convention is a dictionary of strings.
@@ -80,7 +80,7 @@ Will place this in the "opt" value
{"key":"value", "key2","")
-The -a/--arg option can be used to set strings in the "arg" value. It
+The ` + "`-a`/`--arg`" + ` option can be used to set strings in the "arg" value. It
can be repeated as many times as required. This is useful for rc
commands which take the "arg" parameter which by convention is a list
of strings.
@@ -91,13 +91,13 @@ Will place this in the "arg" value
["value", "value2"]
-Use --loopback to connect to the rclone instance running "rclone rc".
+Use ` + "`--loopback`" + ` to connect to the rclone instance running ` + "`rclone rc`" + `.
This is very useful for testing commands without having to run an
rclone rc server, e.g.:
rclone rc --loopback operations/about fs=/
-Use "rclone rc" to see a list of all possible commands.`,
+Use ` + "`rclone rc`" + ` to see a list of all possible commands.`,
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(0, 1e9, command, args)
cmd.Run(false, false, command, func() error {
diff --git a/cmd/rcat/rcat.go b/cmd/rcat/rcat.go
index ef969b170f6ab..f082fa95d34b5 100644
--- a/cmd/rcat/rcat.go
+++ b/cmd/rcat/rcat.go
@@ -44,11 +44,11 @@ must fit into RAM. The cutoff needs to be small enough to adhere
the limits of your remote, please see there. Generally speaking,
setting this cutoff too high will decrease your performance.
-Use the |--size| flag to preallocate the file in advance at the remote end
+Use the ` + "`--size`" + ` flag to preallocate the file in advance at the remote end
and actually stream it, even if remote backend doesn't support streaming.
-|--size| should be the exact size of the input stream in bytes. If the
-size of the stream is different in length to the |--size| passed in
+` + "`--size`" + ` should be the exact size of the input stream in bytes. If the
+size of the stream is different in length to the ` + "`--size`" + ` passed in
then the transfer will likely fail.
Note that the upload can also not be retried because the data is
diff --git a/cmd/serve/http/data/data.go b/cmd/serve/http/data/data.go
index de8362100fd7d..73f6a1ff28b91 100644
--- a/cmd/serve/http/data/data.go
+++ b/cmd/serve/http/data/data.go
@@ -19,7 +19,7 @@ import (
var Help = `
#### Template
---template allows a user to specify a custom markup template for HTTP
+` + "`--template`" + ` allows a user to specify a custom markup template for HTTP
and WebDAV serve functions. The server exports the following markup
to be used within the template to server pages:
diff --git a/cmd/serve/http/http.go b/cmd/serve/http/http.go
index c809d6c33e3be..8712f8ac82108 100644
--- a/cmd/serve/http/http.go
+++ b/cmd/serve/http/http.go
@@ -51,12 +51,12 @@ var Command = &cobra.Command{
This can be viewed in a web browser or you can make a remote of type
http read from it.
-You can use the filter flags (e.g. --include, --exclude) to control what
+You can use the filter flags (e.g. ` + "`--include`, `--exclude`" + `) to control what
is served.
-The server will log errors. Use -v to see access logs.
+The server will log errors. Use ` + "`-v`" + ` to see access logs.
---bwlimit will be respected for file transfers. Use --stats to
+` + "`--bwlimit`" + ` will be respected for file transfers. Use ` + "`--stats`" + ` to
control the stats printing.
` + httplib.Help + data.Help + auth.Help + vfs.Help,
Run: func(command *cobra.Command, args []string) {
diff --git a/cmd/serve/httplib/httplib.go b/cmd/serve/httplib/httplib.go
index 4e3efe7d02bac..82a29817f86de 100644
--- a/cmd/serve/httplib/httplib.go
+++ b/cmd/serve/httplib/httplib.go
@@ -30,30 +30,30 @@ var ()
var Help = `
### Server options
-Use --addr to specify which IP address and port the server should
-listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all
-IPs. By default it only listens on localhost. You can use port
+Use ` + "`--addr`" + ` to specify which IP address and port the server should
+listen on, e.g. ` + "`--addr 1.2.3.4:8000` or `--addr :8080`" + ` to
+listen to all IPs. By default it only listens on localhost. You can use port
:0 to let the OS choose an available port.
-If you set --addr to listen on a public or LAN accessible IP address
+If you set ` + "`--addr`" + ` to listen on a public or LAN accessible IP address
then using Authentication is advised - see the next section for info.
---server-read-timeout and --server-write-timeout can be used to
+` + "`--server-read-timeout` and `--server-write-timeout`" + ` can be used to
control the timeouts on the server. Note that this is the total time
for a transfer.
---max-header-bytes controls the maximum number of bytes the server will
+` + "`--max-header-bytes`" + ` controls the maximum number of bytes the server will
accept in the HTTP header.
---baseurl controls the URL prefix that rclone serves from. By default
-rclone will serve from the root. If you used --baseurl "/rclone" then
+` + "`--baseurl`" + ` controls the URL prefix that rclone serves from. By default
+rclone will serve from the root. If you used ` + "`--baseurl \"/rclone\"`" + ` then
rclone would serve from a URL starting with "/rclone/". This is
useful if you wish to proxy rclone serve. Rclone automatically
-inserts leading and trailing "/" on --baseurl, so --baseurl "rclone",
---baseurl "/rclone" and --baseurl "/rclone/" are all treated
+inserts leading and trailing "/" on ` + "`--baseurl`" + `, so ` + "`--baseurl \"rclone\"`" + `,
+` + "`--baseurl \"/rclone\"` and `--baseurl \"/rclone/\"`" + ` are all treated
identically.
---template allows a user to specify a custom markup template for HTTP
+` + "`--template`" + ` allows a user to specify a custom markup template for HTTP
and WebDAV serve functions. The server exports the following markup
to be used within the template to server pages:
@@ -81,9 +81,9 @@ to be used within the template to server pages:
By default this will serve files without needing a login.
You can either use an htpasswd file which can take lots of users, or
-set a single username and password with the --user and --pass flags.
+set a single username and password with the ` + "`--user` and `--pass`" + ` flags.
-Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is
+Use ` + "`--htpasswd /path/to/htpasswd`" + ` to provide an htpasswd file. This is
in standard apache format and supports MD5, SHA1 and BCrypt for basic
authentication. Bcrypt is recommended.
@@ -95,18 +95,18 @@ To create an htpasswd file:
The password file can be updated while rclone is running.
-Use --realm to set the authentication realm.
+Use ` + "`--realm`" + ` to set the authentication realm.
#### SSL/TLS
By default this will serve over HTTP. If you want you can serve over
-HTTPS. You will need to supply the --cert and --key flags. If you
-wish to do client side certificate validation then you will need to
-supply --client-ca also.
+HTTPS. You will need to supply the ` + "`--cert` and `--key`" + ` flags.
+If you wish to do client side certificate validation then you will need to
+supply ` + "`--client-ca`" + ` also.
---cert should be either a PEM encoded certificate or a concatenation
-of that with the CA certificate. --key should be the PEM encoded
-private key and --client-ca should be the PEM encoded client
+` + "`--cert`" + ` should be either a PEM encoded certificate or a concatenation
+of that with the CA certificate. ` + "`--key`" + ` should be the PEM encoded
+private key and ` + "`--client-ca`" + ` should be the PEM encoded client
certificate authority certificate.
`
diff --git a/cmd/serve/restic/restic.go b/cmd/serve/restic/restic.go
index 65b60dc053d82..a8eb7b483a663 100644
--- a/cmd/serve/restic/restic.go
+++ b/cmd/serve/restic/restic.go
@@ -59,8 +59,8 @@ backups.
The server will log errors. Use -v to see access logs.
---bwlimit will be respected for file transfers. Use --stats to
-control the stats printing.
+` + "`--bwlimit`" + ` will be respected for file transfers.
+Use ` + "`--stats`" + ` to control the stats printing.
### Setting up rclone for use by restic ###
@@ -79,11 +79,11 @@ Where you can replace "backup" in the above by whatever path in the
remote you wish to use.
By default this will serve on "localhost:8080" you can change this
-with use of the "--addr" flag.
+with use of the ` + "`--addr`" + ` flag.
You might wish to start this server on boot.
-Adding --cache-objects=false will cause rclone to stop caching objects
+Adding ` + "`--cache-objects=false`" + ` will cause rclone to stop caching objects
returned from the List call. Caching is normally desirable as it speeds
up downloading objects, saves transactions and uses very little memory.
@@ -129,7 +129,7 @@ these **must** end with /. Eg
#### Private repositories ####
-The "--private-repos" flag can be used to limit users to repositories starting
+The` + "`--private-repos`" + ` flag can be used to limit users to repositories starting
with a path of ` + "`//`" + `.
` + httplib.Help,
Run: func(command *cobra.Command, args []string) {
diff --git a/cmd/serve/sftp/sftp.go b/cmd/serve/sftp/sftp.go
index cecb757077ac6..c77450a5e2a5d 100644
--- a/cmd/serve/sftp/sftp.go
+++ b/cmd/serve/sftp/sftp.go
@@ -65,17 +65,18 @@ var Command = &cobra.Command{
Long: `Run a SFTP server to serve a remote over SFTP. This can be used
with an SFTP client or you can make a remote of type sftp to use with it.
-You can use the filter flags (e.g. --include, --exclude) to control what
+You can use the filter flags (e.g. ` + "`--include`, `--exclude`" + `) to control what
is served.
-The server will log errors. Use -v to see access logs.
+The server will log errors. Use ` + "`-v`" + ` to see access logs.
---bwlimit will be respected for file transfers. Use --stats to
-control the stats printing.
+` + "`--bwlimit`" + ` will be respected for file transfers.
+Use ` + "`--stats`" + ` to control the stats printing.
-You must provide some means of authentication, either with --user/--pass,
-an authorized keys file (specify location with --authorized-keys - the
-default is the same as ssh), an --auth-proxy, or set the --no-auth flag for no
+You must provide some means of authentication, either with
+` + "`--user`/`--pass`" + `, an authorized keys file (specify location with
+` + "`--authorized-keys`" + ` - the default is the same as ssh), an
+` + "`--auth-proxy`" + `, or set the ` + "`--no-auth`" + ` flag for no
authentication when logging in.
Note that this also implements a small number of shell commands so
@@ -83,30 +84,30 @@ that it can provide md5sum/sha1sum/df information for the rclone sftp
backend. This means that is can support SHA1SUMs, MD5SUMs and the
about command when paired with the rclone sftp backend.
-If you don't supply a host --key then rclone will generate rsa, ecdsa
+If you don't supply a host ` + "`--key`" + ` then rclone will generate rsa, ecdsa
and ed25519 variants, and cache them for later use in rclone's cache
-directory (see "rclone help flags cache-dir") in the "serve-sftp"
+directory (see ` + "`rclone help flags cache-dir`" + `) in the "serve-sftp"
directory.
By default the server binds to localhost:2022 - if you want it to be
-reachable externally then supply "--addr :2022" for example.
+reachable externally then supply ` + "`--addr :2022`" + ` for example.
-Note that the default of "--vfs-cache-mode off" is fine for the rclone
+Note that the default of ` + "`--vfs-cache-mode off`" + ` is fine for the rclone
sftp backend, but it may not be with other SFTP clients.
-If --stdio is specified, rclone will serve SFTP over stdio, which can
+If ` + "`--stdio`" + ` is specified, rclone will serve SFTP over stdio, which can
be used with sshd via ~/.ssh/authorized_keys, for example:
restrict,command="rclone serve sftp --stdio ./photos" ssh-rsa ...
-On the client you need to set "--transfers 1" when using --stdio.
+On the client you need to set ` + "`--transfers 1`" + ` when using ` + "`--stdio`" + `.
Otherwise multiple instances of the rclone server are started by OpenSSH
which can lead to "corrupted on transfer" errors. This is the case because
the client chooses indiscriminately which server to send commands to while
the servers all have different views of the state of the filing system.
The "restrict" in authorized_keys prevents SHA1SUMs and MD5SUMs from beeing
-used. Omitting "restrict" and using --sftp-path-override to enable
+used. Omitting "restrict" and using ` + "`--sftp-path-override`" + ` to enable
checksumming is possible but less secure and you could use the SFTP server
provided by OpenSSH in this case.
diff --git a/lib/http/auth/auth.go b/lib/http/auth/auth.go
index 222d09d2e6f52..e30ee9383f8cc 100644
--- a/lib/http/auth/auth.go
+++ b/lib/http/auth/auth.go
@@ -14,9 +14,9 @@ var Help = `
By default this will serve files without needing a login.
You can either use an htpasswd file which can take lots of users, or
-set a single username and password with the --user and --pass flags.
+set a single username and password with the ` + "`--user` and `--pass`" + ` flags.
-Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is
+Use ` + "`--htpasswd /path/to/htpasswd`" + ` to provide an htpasswd file. This is
in standard apache format and supports MD5, SHA1 and BCrypt for basic
authentication. Bcrypt is recommended.
@@ -28,9 +28,9 @@ To create an htpasswd file:
The password file can be updated while rclone is running.
-Use --realm to set the authentication realm.
+Use ` + "`--realm`" + ` to set the authentication realm.
-Use --salt to change the password hashing salt from the default.
+Use ` + "`--salt`" + ` to change the password hashing salt from the default.
`
// CustomAuthFn if used will be used to authenticate user, pass. If an error
diff --git a/lib/http/http.go b/lib/http/http.go
index d6ff1452aa52b..ac5befd3719f4 100644
--- a/lib/http/http.go
+++ b/lib/http/http.go
@@ -25,39 +25,39 @@ import (
var Help = `
### Server options
-Use --addr to specify which IP address and port the server should
-listen on, eg --addr 1.2.3.4:8000 or --addr :8080 to listen to all
+Use ` + "`--addr`" + ` to specify which IP address and port the server should
+listen on, eg ` + "`--addr 1.2.3.4:8000` or `--addr :8080`" + ` to listen to all
IPs. By default it only listens on localhost. You can use port
:0 to let the OS choose an available port.
-If you set --addr to listen on a public or LAN accessible IP address
+If you set ` + "`--addr`" + ` to listen on a public or LAN accessible IP address
then using Authentication is advised - see the next section for info.
---server-read-timeout and --server-write-timeout can be used to
+` + "`--server-read-timeout` and `--server-write-timeout`" + ` can be used to
control the timeouts on the server. Note that this is the total time
for a transfer.
---max-header-bytes controls the maximum number of bytes the server will
+` + "`--max-header-bytes`" + ` controls the maximum number of bytes the server will
accept in the HTTP header.
---baseurl controls the URL prefix that rclone serves from. By default
-rclone will serve from the root. If you used --baseurl "/rclone" then
+` + "`--baseurl`" + ` controls the URL prefix that rclone serves from. By default
+rclone will serve from the root. If you used ` + "`--baseurl \"/rclone\"`" + ` then
rclone would serve from a URL starting with "/rclone/". This is
useful if you wish to proxy rclone serve. Rclone automatically
-inserts leading and trailing "/" on --baseurl, so --baseurl "rclone",
---baseurl "/rclone" and --baseurl "/rclone/" are all treated
+inserts leading and trailing "/" on ` + "`--baseurl`" + `, so ` + "`--baseurl \"rclone\"`" + `,
+` + "`--baseurl \"/rclone\"` and `--baseurl \"/rclone/\"`" + ` are all treated
identically.
#### SSL/TLS
By default this will serve over http. If you want you can serve over
-https. You will need to supply the --cert and --key flags. If you
-wish to do client side certificate validation then you will need to
-supply --client-ca also.
+https. You will need to supply the ` + "`--cert` and `--key`" + ` flags.
+If you wish to do client side certificate validation then you will need to
+supply ` + "`--client-ca`" + ` also.
---cert should be a either a PEM encoded certificate or a concatenation
-of that with the CA certificate. --key should be the PEM encoded
-private key and --client-ca should be the PEM encoded client
+` + "`--cert`" + ` should be a either a PEM encoded certificate or a concatenation
+of that with the CA certificate. ` + "`--key`" + ` should be the PEM encoded
+private key and ` + "`--client-ca`" + ` should be the PEM encoded client
certificate authority certificate.
`
From 027746ef6e6fbaf5bee33bdd6481dc9c3e8ffa45 Mon Sep 17 00:00:00 2001
From: Abhiraj
Date: Fri, 24 Jun 2022 01:38:09 +0530
Subject: [PATCH 106/560] drive: moved rclone_folder_id to advanced section -
fixes #3463
---
backend/drive/drive.go | 1 +
docs/content/drive.md | 4 +---
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index b352b9004855e..9eac37a0efab4 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -277,6 +277,7 @@ Leave blank normally.
Fill in to access "Computers" folders (see docs), or for rclone to use
a non root folder as its starting point.
`,
+ Advanced: true,
}, {
Name: "service_account_file",
Help: "Service Account Credentials JSON file path.\n\nLeave blank normally.\nNeeded only if you want use SA instead of interactive login." + env.ShellExpandHelp,
diff --git a/docs/content/drive.md b/docs/content/drive.md
index da17114093f03..08d1bb8330cf5 100644
--- a/docs/content/drive.md
+++ b/docs/content/drive.md
@@ -58,8 +58,6 @@ Choose a number from below, or type in your own value
5 | does not allow any access to read or download file content.
\ "drive.metadata.readonly"
scope> 1
-ID of the root folder - leave blank normally. Fill in to access "Computers" folders. (see docs).
-root_folder_id>
Service Account Credentials JSON file path - needed only if you want use SA instead of interactive login.
service_account_file>
Remote config
@@ -161,7 +159,7 @@ directories.
### Root folder ID
-You can set the `root_folder_id` for rclone. This is the directory
+This option has been moved to the advanced section. You can set the `root_folder_id` for rclone. This is the directory
(identified by its `Folder ID`) that rclone considers to be the root
of your drive.
From fdd2f8e6d21d1cf95b33fd2bb09f9c1caef0069d Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Wed, 8 Jun 2022 22:54:39 +0200
Subject: [PATCH 107/560] Error strings should not be capitalized
Reported by staticcheck 2022.1.2 (v0.3.2)
See: staticcheck.io
---
backend/azureblob/azureblob.go | 26 +++++++++++++-------------
backend/azureblob/imds.go | 4 ++--
backend/cache/cache.go | 2 +-
backend/cache/plex.go | 2 +-
backend/cache/storage_persistent.go | 14 +++++++-------
backend/compress/compress.go | 16 ++++++++--------
backend/crypt/cipher.go | 4 ++--
backend/crypt/crypt.go | 2 +-
backend/crypt/pkcs7/pkcs7.go | 10 +++++-----
backend/drive/drive.go | 8 ++++----
backend/dropbox/dropbox.go | 4 ++--
backend/fichier/api.go | 4 ++--
backend/fichier/fichier.go | 2 +-
backend/ftp/ftp.go | 4 ++--
backend/hdfs/fs.go | 2 +-
backend/koofr/koofr.go | 4 ++--
backend/mailru/api/helpers.go | 6 +++---
backend/mailru/mailru.go | 12 ++++++------
backend/qingstor/qingstor.go | 2 +-
backend/qingstor/upload.go | 2 +-
backend/seafile/seafile.go | 8 ++++----
backend/seafile/webapi.go | 2 +-
backend/sftp/sftp.go | 2 +-
backend/swift/swift.go | 2 +-
backend/uptobox/uptobox.go | 6 +++---
cmd/cmd.go | 2 +-
cmd/config/config.go | 2 +-
cmd/cryptdecode/cryptdecode.go | 2 +-
cmd/lsf/lsf.go | 2 +-
cmd/mount/dir.go | 2 +-
cmd/mountlib/check_linux.go | 2 +-
cmd/mountlib/check_other.go | 4 ++--
cmd/rc/rc.go | 2 +-
cmd/serve/ftp/ftp.go | 20 ++++++++++----------
cmd/serve/restic/restic.go | 2 +-
cmd/serve/sftp/connection.go | 2 +-
cmd/settier/settier.go | 2 +-
cmd/tree/tree.go | 6 +++---
fs/accounting/accounting.go | 2 +-
fs/config/configfile/configfile.go | 16 ++++++++--------
fs/config/crypt.go | 6 +++---
fs/cutoffmode.go | 4 ++--
fs/dump.go | 2 +-
fs/dump_test.go | 4 ++--
fs/filter/filter.go | 4 ++--
fs/filter/filter_test.go | 4 ++--
fs/hash/hash.go | 2 +-
fs/log.go | 4 ++--
fs/open_options.go | 10 +++++-----
fs/operations/check.go | 8 ++++----
fs/operations/lsjson.go | 2 +-
fs/operations/operations.go | 10 +++++-----
fs/operations/operations_test.go | 4 ++--
fs/rc/internal.go | 2 +-
fs/rc/rcserver/rcserver_test.go | 2 +-
fs/rc/webgui/webgui.go | 2 +-
fs/sync/sync.go | 2 +-
fstest/mockobject/mockobject.go | 2 +-
fstest/test_all/clean.go | 2 +-
lib/http/http.go | 12 ++++++------
lib/jwtutil/jwtutil.go | 2 +-
lib/rest/url.go | 2 +-
vfs/file.go | 2 +-
vfs/vfscommon/cachemode.go | 4 ++--
vfs/vfsflags/filemode.go | 2 +-
65 files changed, 159 insertions(+), 159 deletions(-)
diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go
index 58f4552b9bd4f..971bdc1d14fd5 100644
--- a/backend/azureblob/azureblob.go
+++ b/backend/azureblob/azureblob.go
@@ -539,10 +539,10 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
err = checkUploadChunkSize(opt.ChunkSize)
if err != nil {
- return nil, fmt.Errorf("azure: chunk size: %w", err)
+ return nil, fmt.Errorf("chunk size: %w", err)
}
if opt.ListChunkSize > maxListChunkSize {
- return nil, fmt.Errorf("azure: blob list size can't be greater than %v - was %v", maxListChunkSize, opt.ListChunkSize)
+ return nil, fmt.Errorf("blob list size can't be greater than %v - was %v", maxListChunkSize, opt.ListChunkSize)
}
if opt.Endpoint == "" {
opt.Endpoint = storageDefaultBaseURL
@@ -551,12 +551,12 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
if opt.AccessTier == "" {
opt.AccessTier = string(defaultAccessTier)
} else if !validateAccessTier(opt.AccessTier) {
- return nil, fmt.Errorf("Azure Blob: Supported access tiers are %s, %s and %s",
+ return nil, fmt.Errorf("supported access tiers are %s, %s and %s",
string(azblob.AccessTierHot), string(azblob.AccessTierCool), string(azblob.AccessTierArchive))
}
if !validatePublicAccess((opt.PublicAccess)) {
- return nil, fmt.Errorf("Azure Blob: Supported public access level are %s and %s",
+ return nil, fmt.Errorf("supported public access level are %s and %s",
string(azblob.PublicAccessBlob), string(azblob.PublicAccessContainer))
}
@@ -598,7 +598,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
case opt.UseEmulator:
credential, err := azblob.NewSharedKeyCredential(emulatorAccount, emulatorAccountKey)
if err != nil {
- return nil, fmt.Errorf("Failed to parse credentials: %w", err)
+ return nil, fmt.Errorf("failed to parse credentials: %w", err)
}
u, err = url.Parse(emulatorBlobEndpoint)
if err != nil {
@@ -644,7 +644,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
})
if err != nil {
- return nil, fmt.Errorf("Failed to acquire MSI token: %w", err)
+ return nil, fmt.Errorf("failed to acquire MSI token: %w", err)
}
u, err = url.Parse(fmt.Sprintf("https://%s.%s", opt.Account, opt.Endpoint))
@@ -679,7 +679,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
case opt.Account != "" && opt.Key != "":
credential, err := azblob.NewSharedKeyCredential(opt.Account, opt.Key)
if err != nil {
- return nil, fmt.Errorf("Failed to parse credentials: %w", err)
+ return nil, fmt.Errorf("failed to parse credentials: %w", err)
}
u, err = url.Parse(fmt.Sprintf("https://%s.%s", opt.Account, opt.Endpoint))
@@ -699,7 +699,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
parts := azblob.NewBlobURLParts(*u)
if parts.ContainerName != "" {
if f.rootContainer != "" && parts.ContainerName != f.rootContainer {
- return nil, errors.New("Container name in SAS URL and container provided in command do not match")
+ return nil, errors.New("container name in SAS URL and container provided in command do not match")
}
containerURL := azblob.NewContainerURL(*u, pipeline)
f.cntURLcache[parts.ContainerName] = &containerURL
@@ -727,7 +727,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
pipe := f.newPipeline(azblob.NewTokenCredential("", tokenRefresher), options)
serviceURL = azblob.NewServiceURL(*u, pipe)
default:
- return nil, errors.New("No authentication method configured")
+ return nil, errors.New("no authentication method configured")
}
f.svcURL = &serviceURL
@@ -1337,7 +1337,7 @@ func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
}
data, err := base64.StdEncoding.DecodeString(o.md5)
if err != nil {
- return "", fmt.Errorf("Failed to decode Content-MD5: %q: %w", o.md5, err)
+ return "", fmt.Errorf("failed to decode Content-MD5: %q: %w", o.md5, err)
}
return hex.EncodeToString(data), nil
}
@@ -1527,7 +1527,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
var offset int64
var count int64
if o.AccessTier() == azblob.AccessTierArchive {
- return nil, fmt.Errorf("Blob in archive tier, you need to set tier to hot or cool first")
+ return nil, fmt.Errorf("blob in archive tier, you need to set tier to hot or cool first")
}
fs.FixRangeOption(options, o.size)
for _, option := range options {
@@ -1752,7 +1752,7 @@ func (o *Object) AccessTier() azblob.AccessTierType {
// SetTier performs changing object tier
func (o *Object) SetTier(tier string) error {
if !validateAccessTier(tier) {
- return fmt.Errorf("Tier %s not supported by Azure Blob Storage", tier)
+ return fmt.Errorf("tier %s not supported by Azure Blob Storage", tier)
}
// Check if current tier already matches with desired tier
@@ -1768,7 +1768,7 @@ func (o *Object) SetTier(tier string) error {
})
if err != nil {
- return fmt.Errorf("Failed to set Blob Tier: %w", err)
+ return fmt.Errorf("failed to set Blob Tier: %w", err)
}
// Set access tier on local object also, this typically
diff --git a/backend/azureblob/imds.go b/backend/azureblob/imds.go
index b23e91d626f3e..84c6379e6d821 100644
--- a/backend/azureblob/imds.go
+++ b/backend/azureblob/imds.go
@@ -119,7 +119,7 @@ func GetMSIToken(ctx context.Context, identity *userMSI) (adal.Token, error) {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
- return result, fmt.Errorf("Couldn't read IMDS response: %w", err)
+ return result, fmt.Errorf("couldn't read IMDS response: %w", err)
}
// Remove BOM, if any. azcopy does this so I'm following along.
b = bytes.TrimPrefix(b, []byte("\xef\xbb\xbf"))
@@ -130,7 +130,7 @@ func GetMSIToken(ctx context.Context, identity *userMSI) (adal.Token, error) {
// storage API call.
err = json.Unmarshal(b, &result)
if err != nil {
- return result, fmt.Errorf("Couldn't unmarshal IMDS response: %w", err)
+ return result, fmt.Errorf("couldn't unmarshal IMDS response: %w", err)
}
return result, nil
diff --git a/backend/cache/cache.go b/backend/cache/cache.go
index fc038f6adc5b4..1c75aea4a8529 100644
--- a/backend/cache/cache.go
+++ b/backend/cache/cache.go
@@ -1128,7 +1128,7 @@ func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (
case fs.Directory:
_ = f.cache.AddDir(DirectoryFromOriginal(ctx, f, o))
default:
- return fmt.Errorf("Unknown object type %T", entry)
+ return fmt.Errorf("unknown object type %T", entry)
}
}
diff --git a/backend/cache/plex.go b/backend/cache/plex.go
index e3a333c73a1b2..09ea472336886 100644
--- a/backend/cache/plex.go
+++ b/backend/cache/plex.go
@@ -213,7 +213,7 @@ func (p *plexConnector) authenticate() error {
var data map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
- return fmt.Errorf("failed to obtain token: %v", err)
+ return fmt.Errorf("failed to obtain token: %w", err)
}
tokenGen, ok := get(data, "user", "authToken")
if !ok {
diff --git a/backend/cache/storage_persistent.go b/backend/cache/storage_persistent.go
index 0de9a39633396..eb36a08063e44 100644
--- a/backend/cache/storage_persistent.go
+++ b/backend/cache/storage_persistent.go
@@ -250,7 +250,7 @@ func (b *Persistent) GetDirEntries(cachedDir *Directory) (fs.DirEntries, error)
if val != nil {
err := json.Unmarshal(val, cachedDir)
if err != nil {
- return fmt.Errorf("error during unmarshalling obj: %v", err)
+ return fmt.Errorf("error during unmarshalling obj: %w", err)
}
} else {
return fmt.Errorf("missing cached dir: %v", cachedDir)
@@ -551,7 +551,7 @@ func (b *Persistent) CleanChunksBySize(maxSize int64) {
err := b.db.Update(func(tx *bolt.Tx) error {
dataTsBucket := tx.Bucket([]byte(DataTsBucket))
if dataTsBucket == nil {
- return fmt.Errorf("Couldn't open (%v) bucket", DataTsBucket)
+ return fmt.Errorf("couldn't open (%v) bucket", DataTsBucket)
}
// iterate through ts
c := dataTsBucket.Cursor()
@@ -901,16 +901,16 @@ func (b *Persistent) rollbackPendingUpload(remote string) error {
v := bucket.Get([]byte(remote))
err = json.Unmarshal(v, tempObj)
if err != nil {
- return fmt.Errorf("pending upload (%v) not found %v", remote, err)
+ return fmt.Errorf("pending upload (%v) not found: %w", remote, err)
}
tempObj.Started = false
v2, err := json.Marshal(tempObj)
if err != nil {
- return fmt.Errorf("pending upload not updated %v", err)
+ return fmt.Errorf("pending upload not updated: %w", err)
}
err = bucket.Put([]byte(tempObj.DestPath), v2)
if err != nil {
- return fmt.Errorf("pending upload not updated %v", err)
+ return fmt.Errorf("pending upload not updated: %w", err)
}
return nil
})
@@ -966,11 +966,11 @@ func (b *Persistent) updatePendingUpload(remote string, fn func(item *tempUpload
}
v2, err := json.Marshal(tempObj)
if err != nil {
- return fmt.Errorf("pending upload not updated %v", err)
+ return fmt.Errorf("pending upload not updated: %w", err)
}
err = bucket.Put([]byte(tempObj.DestPath), v2)
if err != nil {
- return fmt.Errorf("pending upload not updated %v", err)
+ return fmt.Errorf("pending upload not updated: %w", err)
}
return nil
diff --git a/backend/compress/compress.go b/backend/compress/compress.go
index 6d3397dfb0019..dcc54a11c0e9d 100644
--- a/backend/compress/compress.go
+++ b/backend/compress/compress.go
@@ -222,7 +222,7 @@ func processFileName(compressedFileName string) (origFileName string, extension
// Separate the filename and size from the extension
extensionPos := strings.LastIndex(compressedFileName, ".")
if extensionPos == -1 {
- return "", "", 0, errors.New("File name has no extension")
+ return "", "", 0, errors.New("file name has no extension")
}
extension = compressedFileName[extensionPos:]
nameWithSize := compressedFileName[:extensionPos]
@@ -231,11 +231,11 @@ func processFileName(compressedFileName string) (origFileName string, extension
}
match := nameRegexp.FindStringSubmatch(nameWithSize)
if match == nil || len(match) != 3 {
- return "", "", 0, errors.New("Invalid filename")
+ return "", "", 0, errors.New("invalid filename")
}
size, err := base64ToInt64(match[2])
if err != nil {
- return "", "", 0, errors.New("Could not decode size")
+ return "", "", 0, errors.New("could not decode size")
}
return match[1], gzFileExt, size, nil
}
@@ -304,7 +304,7 @@ func (f *Fs) processEntries(entries fs.DirEntries) (newEntries fs.DirEntries, er
case fs.Directory:
f.addDir(&newEntries, x)
default:
- return nil, fmt.Errorf("Unknown object type %T", entry)
+ return nil, fmt.Errorf("unknown object type %T", entry)
}
}
return newEntries, nil
@@ -466,10 +466,10 @@ func (f *Fs) rcat(ctx context.Context, dstFileName string, in io.ReadCloser, mod
_ = os.Remove(tempFile.Name())
}()
if err != nil {
- return nil, fmt.Errorf("Failed to create temporary local FS to spool file: %w", err)
+ return nil, fmt.Errorf("failed to create temporary local FS to spool file: %w", err)
}
if _, err = io.Copy(tempFile, in); err != nil {
- return nil, fmt.Errorf("Failed to write temporary local file: %w", err)
+ return nil, fmt.Errorf("failed to write temporary local file: %w", err)
}
if _, err = tempFile.Seek(0, 0); err != nil {
return nil, err
@@ -720,7 +720,7 @@ func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, opt
if found && (oldObj.(*Object).meta.Mode != Uncompressed || compressible) {
err = oldObj.(*Object).Object.Remove(ctx)
if err != nil {
- return nil, fmt.Errorf("Could remove original object: %w", err)
+ return nil, fmt.Errorf("couldn't remove original object: %w", err)
}
}
@@ -729,7 +729,7 @@ func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, opt
if compressible {
wrapObj, err := operations.Move(ctx, f.Fs, nil, f.dataName(src.Remote(), newObj.size, compressible), newObj.Object)
if err != nil {
- return nil, fmt.Errorf("Couldn't rename streamed Object.: %w", err)
+ return nil, fmt.Errorf("couldn't rename streamed object: %w", err)
}
newObj.Object = wrapObj
}
diff --git a/backend/crypt/cipher.go b/backend/crypt/cipher.go
index 4a3556295b69e..1b265ad19e918 100644
--- a/backend/crypt/cipher.go
+++ b/backend/crypt/cipher.go
@@ -96,7 +96,7 @@ func NewNameEncryptionMode(s string) (mode NameEncryptionMode, err error) {
case "obfuscate":
mode = NameEncryptionObfuscated
default:
- err = fmt.Errorf("Unknown file name encryption mode %q", s)
+ err = fmt.Errorf("unknown file name encryption mode %q", s)
}
return mode, err
}
@@ -162,7 +162,7 @@ func NewNameEncoding(s string) (enc fileNameEncoding, err error) {
case "base32768":
enc = base32768.SafeEncoding
default:
- err = fmt.Errorf("Unknown file name encoding mode %q", s)
+ err = fmt.Errorf("unknown file name encoding mode %q", s)
}
return enc, err
}
diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go
index 11dd55dad9954..33e57cdb9b513 100644
--- a/backend/crypt/crypt.go
+++ b/backend/crypt/crypt.go
@@ -328,7 +328,7 @@ func (f *Fs) encryptEntries(ctx context.Context, entries fs.DirEntries) (newEntr
case fs.Directory:
f.addDir(ctx, &newEntries, x)
default:
- return nil, fmt.Errorf("Unknown object type %T", entry)
+ return nil, fmt.Errorf("unknown object type %T", entry)
}
}
return newEntries, nil
diff --git a/backend/crypt/pkcs7/pkcs7.go b/backend/crypt/pkcs7/pkcs7.go
index db604ae4b6a06..d2a547042ad5e 100644
--- a/backend/crypt/pkcs7/pkcs7.go
+++ b/backend/crypt/pkcs7/pkcs7.go
@@ -8,11 +8,11 @@ import "errors"
// Errors Unpad can return
var (
- ErrorPaddingNotFound = errors.New("Bad PKCS#7 padding - not padded")
- ErrorPaddingNotAMultiple = errors.New("Bad PKCS#7 padding - not a multiple of blocksize")
- ErrorPaddingTooLong = errors.New("Bad PKCS#7 padding - too long")
- ErrorPaddingTooShort = errors.New("Bad PKCS#7 padding - too short")
- ErrorPaddingNotAllTheSame = errors.New("Bad PKCS#7 padding - not all the same")
+ ErrorPaddingNotFound = errors.New("bad PKCS#7 padding - not padded")
+ ErrorPaddingNotAMultiple = errors.New("bad PKCS#7 padding - not a multiple of blocksize")
+ ErrorPaddingTooLong = errors.New("bad PKCS#7 padding - too long")
+ ErrorPaddingTooShort = errors.New("bad PKCS#7 padding - too short")
+ ErrorPaddingNotAllTheSame = errors.New("bad PKCS#7 padding - not all the same")
)
// Pad buf using PKCS#7 to a multiple of n.
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index 9eac37a0efab4..918faddf95b4e 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -2218,10 +2218,10 @@ func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo,
exportExt, _, _ = f.findExportFormatByMimeType(ctx, importMimeType)
if exportExt == "" {
- return nil, fmt.Errorf("No export format found for %q", importMimeType)
+ return nil, fmt.Errorf("no export format found for %q", importMimeType)
}
if exportExt != srcExt && !f.opt.AllowImportNameChange {
- return nil, fmt.Errorf("Can't convert %q to a document with a different export filetype (%q)", srcExt, exportExt)
+ return nil, fmt.Errorf("can't convert %q to a document with a different export filetype (%q)", srcExt, exportExt)
}
}
}
@@ -2526,7 +2526,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
// result of List()
func (f *Fs) Purge(ctx context.Context, dir string) error {
if f.opt.TrashedOnly {
- return errors.New("Can't purge with --drive-trashed-only. Use delete if you want to selectively delete files")
+ return errors.New("can't purge with --drive-trashed-only, use delete if you want to selectively delete files")
}
return f.purgeCheck(ctx, dir, false)
}
@@ -3715,7 +3715,7 @@ func (o *baseObject) open(ctx context.Context, url string, options ...fs.OpenOpt
url += "acknowledgeAbuse=true"
_, res, err = o.httpResponse(ctx, url, "GET", options)
} else {
- err = fmt.Errorf("Use the --drive-acknowledge-abuse flag to download this file: %w", err)
+ err = fmt.Errorf("use the --drive-acknowledge-abuse flag to download this file: %w", err)
}
}
if err != nil {
diff --git a/backend/dropbox/dropbox.go b/backend/dropbox/dropbox.go
index 69d0b4e7d326c..bcee49821d9af 100644
--- a/backend/dropbox/dropbox.go
+++ b/backend/dropbox/dropbox.go
@@ -1199,7 +1199,7 @@ func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration,
return
}
if len(listRes.Links) == 0 {
- err = errors.New("Dropbox says the sharing link already exists, but list came back empty")
+ err = errors.New("sharing link already exists, but list came back empty")
return
}
linkRes = listRes.Links[0]
@@ -1211,7 +1211,7 @@ func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration,
case *sharing.FolderLinkMetadata:
link = res.Url
default:
- err = fmt.Errorf("Don't know how to extract link, response has unknown format: %T", res)
+ err = fmt.Errorf("don't know how to extract link, response has unknown format: %T", res)
}
}
return
diff --git a/backend/fichier/api.go b/backend/fichier/api.go
index 15c916c736314..1ee812c47de5d 100644
--- a/backend/fichier/api.go
+++ b/backend/fichier/api.go
@@ -487,7 +487,7 @@ func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, fileName,
fileName = f.opt.Enc.FromStandardName(fileName)
if len(uploadID) > 10 || !isAlphaNumeric(uploadID) {
- return nil, errors.New("Invalid UploadID")
+ return nil, errors.New("invalid UploadID")
}
opts := rest.Opts{
@@ -529,7 +529,7 @@ func (f *Fs) endUpload(ctx context.Context, uploadID string, nodeurl string) (re
// fs.Debugf(f, "Ending File Upload `%s`", uploadID)
if len(uploadID) > 10 || !isAlphaNumeric(uploadID) {
- return nil, errors.New("Invalid UploadID")
+ return nil, errors.New("invalid UploadID")
}
opts := rest.Opts{
diff --git a/backend/fichier/fichier.go b/backend/fichier/fichier.go
index 8548d00c2b7d3..454e391ff61ca 100644
--- a/backend/fichier/fichier.go
+++ b/backend/fichier/fichier.go
@@ -294,7 +294,7 @@ func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
path, ok := f.dirCache.GetInv(directoryID)
if !ok {
- return nil, errors.New("Cannot find dir in dircache")
+ return nil, errors.New("cannot find dir in dircache")
}
return f.newObjectFromFile(ctx, path, file), nil
diff --git a/backend/ftp/ftp.go b/backend/ftp/ftp.go
index c078f78b095f0..d92678f08a71b 100644
--- a/backend/ftp/ftp.go
+++ b/backend/ftp/ftp.go
@@ -487,7 +487,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (ff fs.Fs
protocol = "ftps://"
}
if opt.TLS && opt.ExplicitTLS {
- return nil, errors.New("Implicit TLS and explicit TLS are mutually incompatible. Please revise your config")
+ return nil, errors.New("implicit TLS and explicit TLS are mutually incompatible, please revise your config")
}
var tlsConfig *tls.Config
if opt.TLS || opt.ExplicitTLS {
@@ -718,7 +718,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
case <-timer.C:
// if timer fired assume no error but connection dead
fs.Errorf(f, "Timeout when waiting for List")
- return nil, errors.New("Timeout when waiting for List")
+ return nil, errors.New("timeout when waiting for List")
}
// Annoyingly FTP returns success for a directory which
diff --git a/backend/hdfs/fs.go b/backend/hdfs/fs.go
index 8de9ddbacf03d..a68372db69aaf 100644
--- a/backend/hdfs/fs.go
+++ b/backend/hdfs/fs.go
@@ -92,7 +92,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
if opt.ServicePrincipalName != "" {
options.KerberosClient, err = getKerberosClient()
if err != nil {
- return nil, fmt.Errorf("Problem with kerberos authentication: %s", err)
+ return nil, fmt.Errorf("problem with kerberos authentication: %w", err)
}
options.KerberosServicePrincipleName = opt.ServicePrincipalName
diff --git a/backend/koofr/koofr.go b/backend/koofr/koofr.go
index f77ae019ff9df..f893b898b4896 100644
--- a/backend/koofr/koofr.go
+++ b/backend/koofr/koofr.go
@@ -351,9 +351,9 @@ func NewFsFromOptions(ctx context.Context, name, root string, opt *Options) (ff
}
if f.mountID == "" {
if opt.MountID == "" {
- return nil, errors.New("Failed to find primary mount")
+ return nil, errors.New("failed to find primary mount")
}
- return nil, errors.New("Failed to find mount " + opt.MountID)
+ return nil, errors.New("failed to find mount " + opt.MountID)
}
rootFile, err := f.client.FilesInfo(f.mountID, f.opt.Enc.FromStandardPath("/"+f.root))
if err == nil && rootFile.Type != "dir" {
diff --git a/backend/mailru/api/helpers.go b/backend/mailru/api/helpers.go
index 1b43eb182a8f2..d7164185fe2dd 100644
--- a/backend/mailru/api/helpers.go
+++ b/backend/mailru/api/helpers.go
@@ -16,9 +16,9 @@ import (
// protocol errors
var (
- ErrorPrematureEOF = errors.New("Premature EOF")
- ErrorInvalidLength = errors.New("Invalid length")
- ErrorZeroTerminate = errors.New("String must end with zero")
+ ErrorPrematureEOF = errors.New("premature EOF")
+ ErrorInvalidLength = errors.New("invalid length")
+ ErrorZeroTerminate = errors.New("string must end with zero")
)
// BinWriter is a binary protocol writer
diff --git a/backend/mailru/mailru.go b/backend/mailru/mailru.go
index 24693c67cfcb8..a9a9bab2dc38d 100644
--- a/backend/mailru/mailru.go
+++ b/backend/mailru/mailru.go
@@ -435,10 +435,10 @@ func (f *Fs) authorize(ctx context.Context, force bool) (err error) {
t, err = oauthConfig.PasswordCredentialsToken(ctx, f.opt.Username, f.opt.Password)
}
if err == nil && !tokenIsValid(t) {
- err = errors.New("Invalid token")
+ err = errors.New("invalid token")
}
if err != nil {
- return fmt.Errorf("Failed to authorize: %w", err)
+ return fmt.Errorf("failed to authorize: %w", err)
}
if err = oauthutil.PutToken(f.name, f.m, t, false); err != nil {
@@ -580,7 +580,7 @@ func readBodyWord(res *http.Response) (word string, err error) {
word = strings.Split(line, " ")[0]
}
if word == "" {
- return "", errors.New("Empty reply from dispatcher")
+ return "", errors.New("empty reply from dispatcher")
}
return word, nil
}
@@ -1684,7 +1684,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
spoolFile, mrHash, err := makeTempFile(ctx, tmpFs, wrapIn, src)
if err != nil {
- return fmt.Errorf("Failed to create spool file: %w", err)
+ return fmt.Errorf("failed to create spool file: %w", err)
}
if o.putByHash(ctx, mrHash, src, "spool") {
// If put by hash is successful, ignore transitive error
@@ -1966,7 +1966,7 @@ func (o *Object) readMetaData(ctx context.Context, force bool) error {
return fs.ErrorIsDir
}
if newObj.remote != o.remote {
- return fmt.Errorf("File %q path has changed to %q", o.remote, newObj.remote)
+ return fmt.Errorf("file %q path has changed to %q", o.remote, newObj.remote)
}
o.hasMetaData = true
o.size = newObj.size
@@ -2318,7 +2318,7 @@ func (p *serverPool) Dispatch(ctx context.Context, current string) (string, erro
})
if err != nil || url == "" {
closeBody(res)
- return "", fmt.Errorf("Failed to request file server: %w", err)
+ return "", fmt.Errorf("failed to request file server: %w", err)
}
p.addServer(url, now)
diff --git a/backend/qingstor/qingstor.go b/backend/qingstor/qingstor.go
index 5f5134099be09..9e9b0c6cc9b0a 100644
--- a/backend/qingstor/qingstor.go
+++ b/backend/qingstor/qingstor.go
@@ -253,7 +253,7 @@ func qsServiceConnection(ctx context.Context, opt *Options) (*qs.Service, error)
_protocol, _host, _port, err := qsParseEndpoint(endpoint)
if err != nil {
- return nil, fmt.Errorf("The endpoint \"%s\" format error", endpoint)
+ return nil, fmt.Errorf("the endpoint \"%s\" format error", endpoint)
}
if _protocol != "" {
diff --git a/backend/qingstor/upload.go b/backend/qingstor/upload.go
index d9a81801950d4..797e718f89b79 100644
--- a/backend/qingstor/upload.go
+++ b/backend/qingstor/upload.go
@@ -184,7 +184,7 @@ func (u *uploader) upload() error {
fs.Debugf(u, "Uploading as single part object to QingStor")
return u.singlePartUpload(reader, u.readerPos)
} else if err != nil {
- return fmt.Errorf("read upload data failed: %s", err)
+ return fmt.Errorf("read upload data failed: %w", err)
}
fs.Debugf(u, "Uploading as multi-part object to QingStor")
diff --git a/backend/seafile/seafile.go b/backend/seafile/seafile.go
index 15a7086411484..ffae61a5ae1dc 100644
--- a/backend/seafile/seafile.go
+++ b/backend/seafile/seafile.go
@@ -886,7 +886,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
// 1- rename source
err = srcFs.renameDir(ctx, srcLibraryID, srcPath, tempName)
if err != nil {
- return fmt.Errorf("Cannot rename source directory to a temporary name: %w", err)
+ return fmt.Errorf("cannot rename source directory to a temporary name: %w", err)
}
// 2- move source to destination
@@ -900,7 +900,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
// 3- rename destination back to source name
err = f.renameDir(ctx, dstLibraryID, path.Join(dstDir, tempName), dstName)
if err != nil {
- return fmt.Errorf("Cannot rename temporary directory to destination name: %w", err)
+ return fmt.Errorf("cannot rename temporary directory to destination name: %w", err)
}
return nil
@@ -923,7 +923,7 @@ func (f *Fs) Purge(ctx context.Context, dir string) error {
// CleanUp the trash in the Fs
func (f *Fs) CleanUp(ctx context.Context) error {
if f.libraryName == "" {
- return errors.New("Cannot clean up at the root of the seafile server: please select a library to clean up")
+ return errors.New("cannot clean up at the root of the seafile server, please select a library to clean up")
}
libraryID, err := f.getLibraryID(ctx, f.libraryName)
if err != nil {
@@ -972,7 +972,7 @@ func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration,
libraryName, filePath := f.splitPath(remote)
if libraryName == "" {
// We cannot share the whole seafile server, we need at least a library
- return "", errors.New("Cannot share the root of the seafile server. Please select a library to share")
+ return "", errors.New("cannot share the root of the seafile server, please select a library to share")
}
libraryID, err := f.getLibraryID(ctx, libraryName)
if err != nil {
diff --git a/backend/seafile/webapi.go b/backend/seafile/webapi.go
index 29d2f8645be1b..c1eb33da0a934 100644
--- a/backend/seafile/webapi.go
+++ b/backend/seafile/webapi.go
@@ -26,7 +26,7 @@ const (
// Errors specific to seafile fs
var (
- ErrorInternalDuringUpload = errors.New("Internal server error during file upload")
+ ErrorInternalDuringUpload = errors.New("internal server error during file upload")
)
// ==================== Seafile API ====================
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index 74af5fa4104a6..bb5add94ddc7c 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -515,7 +515,7 @@ func (f *Fs) setEnv(s *ssh.Session) error {
// fs.Debugf(f, "Setting env %q = %q", env[:equal], env[equal+1:])
err := s.Setenv(env[:equal], env[equal+1:])
if err != nil {
- return fmt.Errorf("Failed to set env var %q: %w", env[:equal], err)
+ return fmt.Errorf("failed to set env var %q: %w", env[:equal], err)
}
}
return nil
diff --git a/backend/swift/swift.go b/backend/swift/swift.go
index 074e2daf7f9c6..e4bae345e8be6 100644
--- a/backend/swift/swift.go
+++ b/backend/swift/swift.go
@@ -1303,7 +1303,7 @@ func (o *Object) getSegmentsDlo(ctx context.Context) (segmentsContainer string,
}
delimiter := strings.Index(dirManifest, "/")
if len(dirManifest) == 0 || delimiter < 0 {
- err = errors.New("Missing or wrong structure of manifest of Dynamic large object")
+ err = errors.New("missing or wrong structure of manifest of Dynamic large object")
return
}
return dirManifest[:delimiter], dirManifest[delimiter+1:], nil
diff --git a/backend/uptobox/uptobox.go b/backend/uptobox/uptobox.go
index 56c4f80a1ec4d..e232ac83cd460 100644
--- a/backend/uptobox/uptobox.go
+++ b/backend/uptobox/uptobox.go
@@ -474,7 +474,7 @@ func (f *Fs) updateFileInformation(ctx context.Context, update *api.UpdateFileIn
func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size int64, options ...fs.OpenOption) (fs.Object, error) {
if size > int64(200e9) { // max size 200GB
- return nil, errors.New("File too big, cant upload")
+ return nil, errors.New("file too big, can't upload")
} else if size == 0 {
return nil, fs.ErrorCantUploadEmptyFiles
}
@@ -497,7 +497,7 @@ func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size
return nil, err
}
if info.StatusCode != 0 {
- return nil, fmt.Errorf("putUnchecked: api error: %d - %s", info.StatusCode, info.Message)
+ return nil, fmt.Errorf("putUnchecked api error: %d - %s", info.StatusCode, info.Message)
}
// we need to have a safe name for the upload to work
tmpName := "rcloneTemp" + random.String(8)
@@ -506,7 +506,7 @@ func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size
return nil, err
}
if len(upload.Files) != 1 {
- return nil, errors.New("Upload: unexpected response")
+ return nil, errors.New("upload unexpected response")
}
match := f.IDRegexp.FindStringSubmatch(upload.Files[0].URL)
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 82ae1ad838712..5dd120be7fd6a 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -117,7 +117,7 @@ func newFsFileAddFilter(remote string) (fs.Fs, string) {
f, fileName := NewFsFile(remote)
if fileName != "" {
if !fi.InActive() {
- err := fmt.Errorf("Can't limit to single files when using filters: %v", remote)
+ err := fmt.Errorf("can't limit to single files when using filters: %v", remote)
err = fs.CountError(err)
log.Fatalf(err.Error())
}
diff --git a/cmd/config/config.go b/cmd/config/config.go
index 96734e5528686..ed57003278043 100644
--- a/cmd/config/config.go
+++ b/cmd/config/config.go
@@ -402,7 +402,7 @@ To reconnect use "rclone config reconnect".
}
err := doDisconnect(context.Background())
if err != nil {
- return fmt.Errorf("Disconnect call failed: %w", err)
+ return fmt.Errorf("disconnect call failed: %w", err)
}
return nil
},
diff --git a/cmd/cryptdecode/cryptdecode.go b/cmd/cryptdecode/cryptdecode.go
index 2f938cbfa7a0a..d36c70ca47f52 100644
--- a/cmd/cryptdecode/cryptdecode.go
+++ b/cmd/cryptdecode/cryptdecode.go
@@ -48,7 +48,7 @@ See the documentation on the [crypt](/crypt/) overlay for more info.
return err
}
if fsInfo.Name != "crypt" {
- return errors.New("The remote needs to be of type \"crypt\"")
+ return errors.New("the remote needs to be of type \"crypt\"")
}
cipher, err := crypt.NewCipher(config)
if err != nil {
diff --git a/cmd/lsf/lsf.go b/cmd/lsf/lsf.go
index 812edf8eab748..732d2b5e8980a 100644
--- a/cmd/lsf/lsf.go
+++ b/cmd/lsf/lsf.go
@@ -199,7 +199,7 @@ func Lsf(ctx context.Context, fsrc fs.Fs, out io.Writer) error {
case 'T':
list.AddTier()
default:
- return fmt.Errorf("Unknown format character %q", char)
+ return fmt.Errorf("unknown format character %q", char)
}
}
diff --git a/cmd/mount/dir.go b/cmd/mount/dir.go
index 47ec4bf2da5d2..b82b521796f13 100644
--- a/cmd/mount/dir.go
+++ b/cmd/mount/dir.go
@@ -199,7 +199,7 @@ func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fusefs
defer log.Trace(d, "oldName=%q, newName=%q, newDir=%+v", req.OldName, req.NewName, newDir)("err=%v", &err)
destDir, ok := newDir.(*Dir)
if !ok {
- return fmt.Errorf("Unknown Dir type %T", newDir)
+ return fmt.Errorf("unknown Dir type %T", newDir)
}
err = d.Dir.Rename(req.OldName, req.NewName, destDir.Dir)
diff --git a/cmd/mountlib/check_linux.go b/cmd/mountlib/check_linux.go
index 3cdb346905d8a..1f241af8723bc 100644
--- a/cmd/mountlib/check_linux.go
+++ b/cmd/mountlib/check_linux.go
@@ -22,7 +22,7 @@ const (
// On Linux we use the OS-specific /proc/mount API so the check won't access the path.
// Directories marked as "mounted" by autofs are considered not mounted.
func CheckMountEmpty(mountpoint string) error {
- const msg = "Directory already mounted, use --allow-non-empty to mount anyway: %s"
+ const msg = "directory already mounted, use --allow-non-empty to mount anyway: %s"
mountpointAbs, err := filepath.Abs(mountpoint)
if err != nil {
diff --git a/cmd/mountlib/check_other.go b/cmd/mountlib/check_other.go
index 0b7eeede71eb8..c8876632cb3d2 100644
--- a/cmd/mountlib/check_other.go
+++ b/cmd/mountlib/check_other.go
@@ -17,7 +17,7 @@ import (
func CheckMountEmpty(mountpoint string) error {
fp, err := os.Open(mountpoint)
if err != nil {
- return fmt.Errorf("Can not open: %s: %w", mountpoint, err)
+ return fmt.Errorf("cannot open: %s: %w", mountpoint, err)
}
defer fs.CheckClose(fp, &err)
@@ -26,7 +26,7 @@ func CheckMountEmpty(mountpoint string) error {
return nil
}
- const msg = "Directory is not empty, use --allow-non-empty to mount anyway: %s"
+ const msg = "directory is not empty, use --allow-non-empty to mount anyway: %s"
if err == nil {
return fmt.Errorf(msg, mountpoint)
}
diff --git a/cmd/rc/rc.go b/cmd/rc/rc.go
index 6b123f97e3ffb..53e8697d80095 100644
--- a/cmd/rc/rc.go
+++ b/cmd/rc/rc.go
@@ -211,7 +211,7 @@ func doCall(ctx context.Context, path string, in rc.Params) (out rc.Params, err
bodyString = err.Error()
}
bodyString = strings.TrimSpace(bodyString)
- return nil, fmt.Errorf("Failed to read rc response: %s: %s", resp.Status, bodyString)
+ return nil, fmt.Errorf("failed to read rc response: %s: %s", resp.Status, bodyString)
}
// Parse output
diff --git a/cmd/serve/ftp/ftp.go b/cmd/serve/ftp/ftp.go
index f8ba817259cad..05f7b092405d9 100644
--- a/cmd/serve/ftp/ftp.go
+++ b/cmd/serve/ftp/ftp.go
@@ -135,11 +135,11 @@ var passivePortsRe = regexp.MustCompile(`^\s*\d+\s*-\s*\d+\s*$`)
func newServer(ctx context.Context, f fs.Fs, opt *Options) (*server, error) {
host, port, err := net.SplitHostPort(opt.ListenAddr)
if err != nil {
- return nil, errors.New("Failed to parse host:port")
+ return nil, errors.New("failed to parse host:port")
}
portNum, err := strconv.Atoi(port)
if err != nil {
- return nil, errors.New("Failed to parse host:port")
+ return nil, errors.New("failed to parse host:port")
}
s := &server{
@@ -284,7 +284,7 @@ func (d *Driver) ChangeDir(path string) (err error) {
return err
}
if !n.IsDir() {
- return errors.New("Not a directory")
+ return errors.New("not a directory")
}
return nil
}
@@ -296,12 +296,12 @@ func (d *Driver) ListDir(path string, callback func(ftp.FileInfo) error) (err er
defer log.Trace(path, "")("err = %v", &err)
node, err := d.vfs.Stat(path)
if err == vfs.ENOENT {
- return errors.New("Directory not found")
+ return errors.New("directory not found")
} else if err != nil {
return err
}
if !node.IsDir() {
- return errors.New("Not a directory")
+ return errors.New("not a directory")
}
dir := node.(*vfs.Dir)
@@ -335,7 +335,7 @@ func (d *Driver) DeleteDir(path string) (err error) {
return err
}
if !node.IsDir() {
- return errors.New("Not a directory")
+ return errors.New("not a directory")
}
err = node.Remove()
if err != nil {
@@ -354,7 +354,7 @@ func (d *Driver) DeleteFile(path string) (err error) {
return err
}
if !node.IsFile() {
- return errors.New("Not a file")
+ return errors.New("not a file")
}
err = node.Remove()
if err != nil {
@@ -392,12 +392,12 @@ func (d *Driver) GetFile(path string, offset int64) (size int64, fr io.ReadClose
node, err := d.vfs.Stat(path)
if err == vfs.ENOENT {
fs.Infof(path, "File not found")
- return 0, nil, errors.New("File not found")
+ return 0, nil, errors.New("file not found")
} else if err != nil {
return 0, nil, err
}
if !node.IsFile() {
- return 0, nil, errors.New("Not a file")
+ return 0, nil, errors.New("not a file")
}
handle, err := node.Open(os.O_RDONLY)
@@ -426,7 +426,7 @@ func (d *Driver) PutFile(path string, data io.Reader, appendData bool) (n int64,
if err == nil {
isExist = true
if node.IsDir() {
- return 0, errors.New("A dir has the same name")
+ return 0, errors.New("a dir has the same name")
}
} else {
if os.IsNotExist(err) {
diff --git a/cmd/serve/restic/restic.go b/cmd/serve/restic/restic.go
index a8eb7b483a663..7139b084b8e5d 100644
--- a/cmd/serve/restic/restic.go
+++ b/cmd/serve/restic/restic.go
@@ -139,7 +139,7 @@ with a path of ` + "`//`" + `.
s := NewServer(f, &httpflags.Opt)
if stdio {
if terminal.IsTerminal(int(os.Stdout.Fd())) {
- return errors.New("Refusing to run HTTP2 server directly on a terminal, please let restic start rclone")
+ return errors.New("refusing to run HTTP2 server directly on a terminal, please let restic start rclone")
}
conn := &StdioConn{
diff --git a/cmd/serve/sftp/connection.go b/cmd/serve/sftp/connection.go
index 693b5fbb06a8c..d8be2bce5831b 100644
--- a/cmd/serve/sftp/connection.go
+++ b/cmd/serve/sftp/connection.go
@@ -74,7 +74,7 @@ func (c *conn) execCommand(ctx context.Context, out io.Writer, command string) (
}
usage, err := about(ctx)
if err != nil {
- return fmt.Errorf("About failed: %w", err)
+ return fmt.Errorf("about failed: %w", err)
}
total, used, free := int64(-1), int64(-1), int64(-1)
if usage.Total != nil {
diff --git a/cmd/settier/settier.go b/cmd/settier/settier.go
index 97953727a09be..6be1c1c6a2951 100644
--- a/cmd/settier/settier.go
+++ b/cmd/settier/settier.go
@@ -47,7 +47,7 @@ Or just provide remote directory and all files in directory will be tiered
cmd.Run(false, false, command, func() error {
isSupported := fsrc.Features().SetTier
if !isSupported {
- return fmt.Errorf("Remote %s does not support settier", fsrc.Name())
+ return fmt.Errorf("remote %s does not support settier", fsrc.Name())
}
return operations.SetTier(context.Background(), fsrc, tier)
diff --git a/cmd/tree/tree.go b/cmd/tree/tree.go
index 4bd8994626556..2528d9dc2a42d 100644
--- a/cmd/tree/tree.go
+++ b/cmd/tree/tree.go
@@ -102,7 +102,7 @@ For a more interactive navigation of the remote see the
var err error
outFile, err = os.Create(outFileName)
if err != nil {
- return fmt.Errorf("failed to create output file: %v", err)
+ return fmt.Errorf("failed to create output file: %w", err)
}
}
opts.VerSort = opts.VerSort || sort == "version"
@@ -209,7 +209,7 @@ func (dirs Fs) Stat(filePath string) (fi os.FileInfo, err error) {
}
_, entry := dirtree.DirTree(dirs).Find(filePath)
if entry == nil {
- return nil, fmt.Errorf("Couldn't find %q in directory cache", filePath)
+ return nil, fmt.Errorf("couldn't find %q in directory cache", filePath)
}
return &FileInfo{entry}, nil
}
@@ -221,7 +221,7 @@ func (dirs Fs) ReadDir(dir string) (names []string, err error) {
dir = strings.TrimLeft(dir, "/")
entries, ok := dirs[dir]
if !ok {
- return nil, fmt.Errorf("Couldn't find directory %q", dir)
+ return nil, fmt.Errorf("couldn't find directory %q", dir)
}
for _, entry := range entries {
names = append(names, path.Base(entry.Remote()))
diff --git a/fs/accounting/accounting.go b/fs/accounting/accounting.go
index 53d6549fb65d0..a019232fb27e8 100644
--- a/fs/accounting/accounting.go
+++ b/fs/accounting/accounting.go
@@ -19,7 +19,7 @@ import (
// ErrorMaxTransferLimitReached defines error when transfer limit is reached.
// Used for checking on exit and matching to correct exit code.
-var ErrorMaxTransferLimitReached = errors.New("Max transfer limit reached as set by --max-transfer")
+var ErrorMaxTransferLimitReached = errors.New("max transfer limit reached as set by --max-transfer")
// ErrorMaxTransferLimitReachedFatal is returned from Read when the max
// transfer limit is reached.
diff --git a/fs/config/configfile/configfile.go b/fs/config/configfile/configfile.go
index df01f25a5499e..e9c498995f3a3 100644
--- a/fs/config/configfile/configfile.go
+++ b/fs/config/configfile/configfile.go
@@ -106,7 +106,7 @@ func (s *Storage) Save() error {
configPath := config.GetConfigPath()
if configPath == "" {
- return fmt.Errorf("Failed to save config file: Path is empty")
+ return fmt.Errorf("failed to save config file, path is empty")
}
dir, name := filepath.Split(configPath)
@@ -116,18 +116,18 @@ func (s *Storage) Save() error {
}
f, err := ioutil.TempFile(dir, name)
if err != nil {
- return fmt.Errorf("Failed to create temp file for new config: %v", err)
+ return fmt.Errorf("failed to create temp file for new config: %w", err)
}
defer func() {
_ = f.Close()
if err := os.Remove(f.Name()); err != nil && !os.IsNotExist(err) {
- fs.Errorf(nil, "Failed to remove temp config file: %v", err)
+ fs.Errorf(nil, "failed to remove temp config file: %v", err)
}
}()
var buf bytes.Buffer
if err := goconfig.SaveConfigData(s.gc, &buf); err != nil {
- return fmt.Errorf("Failed to save config file: %v", err)
+ return fmt.Errorf("failed to save config file: %w", err)
}
if err := config.Encrypt(&buf, f); err != nil {
@@ -137,7 +137,7 @@ func (s *Storage) Save() error {
_ = f.Sync()
err = f.Close()
if err != nil {
- return fmt.Errorf("Failed to close config file: %v", err)
+ return fmt.Errorf("failed to close config file: %w", err)
}
var fileMode os.FileMode = 0600
@@ -157,10 +157,10 @@ func (s *Storage) Save() error {
}
if err = os.Rename(configPath, configPath+".old"); err != nil && !os.IsNotExist(err) {
- return fmt.Errorf("Failed to move previous config to backup location: %v", err)
+ return fmt.Errorf("failed to move previous config to backup location: %w", err)
}
if err = os.Rename(f.Name(), configPath); err != nil {
- return fmt.Errorf("Failed to move newly written config from %s to final location: %v", f.Name(), err)
+ return fmt.Errorf("failed to move newly written config from %s to final location: %v", f.Name(), err)
}
if err := os.Remove(configPath + ".old"); err != nil && !os.IsNotExist(err) {
fs.Errorf(nil, "Failed to remove backup config file: %v", err)
@@ -177,7 +177,7 @@ func (s *Storage) Serialize() (string, error) {
s.check()
var buf bytes.Buffer
if err := goconfig.SaveConfigData(s.gc, &buf); err != nil {
- return "", fmt.Errorf("Failed to save config file: %v", err)
+ return "", fmt.Errorf("failed to save config file: %w", err)
}
return buf.String(), nil
diff --git a/fs/config/crypt.go b/fs/config/crypt.go
index 002e6c49fd71d..6792811dcef5c 100644
--- a/fs/config/crypt.go
+++ b/fs/config/crypt.go
@@ -133,7 +133,7 @@ func Decrypt(b io.ReadSeeker) (io.Reader, error) {
return nil, fmt.Errorf("failed to load base64 encoded data: %w", err)
}
if len(box) < 24+secretbox.Overhead {
- return nil, errors.New("Configuration data too short")
+ return nil, errors.New("configuration data too short")
}
var out []byte
@@ -206,7 +206,7 @@ func Encrypt(src io.Reader, dst io.Writer) error {
enc := base64.NewEncoder(base64.StdEncoding, dst)
_, err := enc.Write(nonce[:])
if err != nil {
- return fmt.Errorf("Failed to write config file: %v", err)
+ return fmt.Errorf("failed to write config file: %w", err)
}
var key [32]byte
@@ -219,7 +219,7 @@ func Encrypt(src io.Reader, dst io.Writer) error {
b := secretbox.Seal(nil, data, &nonce, &key)
_, err = enc.Write(b)
if err != nil {
- return fmt.Errorf("Failed to write config file: %v", err)
+ return fmt.Errorf("failed to write config file: %w", err)
}
return enc.Close()
}
diff --git a/fs/cutoffmode.go b/fs/cutoffmode.go
index a58c5c4520697..9f6390663c70d 100644
--- a/fs/cutoffmode.go
+++ b/fs/cutoffmode.go
@@ -38,7 +38,7 @@ func (m *CutoffMode) Set(s string) error {
return nil
}
}
- return fmt.Errorf("Unknown cutoff mode %q", s)
+ return fmt.Errorf("unknown cutoff mode %q", s)
}
// Type of the value
@@ -50,7 +50,7 @@ func (m *CutoffMode) Type() string {
func (m *CutoffMode) UnmarshalJSON(in []byte) error {
return UnmarshalJSONFlag(in, m, func(i int64) error {
if i < 0 || i >= int64(len(cutoffModeToString)) {
- return fmt.Errorf("Out of range cutoff mode %d", i)
+ return fmt.Errorf("out of range cutoff mode %d", i)
}
*m = (CutoffMode)(i)
return nil
diff --git a/fs/dump.go b/fs/dump.go
index f0c72bcaba789..a156712533c36 100644
--- a/fs/dump.go
+++ b/fs/dump.go
@@ -78,7 +78,7 @@ func (f *DumpFlags) Set(s string) error {
}
}
if !found {
- return fmt.Errorf("Unknown dump flag %q", part)
+ return fmt.Errorf("unknown dump flag %q", part)
}
}
*f = flags
diff --git a/fs/dump_test.go b/fs/dump_test.go
index c0ea248dd279c..ede6125e8c731 100644
--- a/fs/dump_test.go
+++ b/fs/dump_test.go
@@ -30,7 +30,7 @@ func TestDumpFlagsSet(t *testing.T) {
{"bodies,headers,auth", DumpBodies | DumpHeaders | DumpAuth, ""},
{"bodies,headers,auth", DumpBodies | DumpHeaders | DumpAuth, ""},
{"headers,bodies,requests,responses,auth,filters", DumpHeaders | DumpBodies | DumpRequests | DumpResponses | DumpAuth | DumpFilters, ""},
- {"headers,bodies,unknown,auth", 0, "Unknown dump flag \"unknown\""},
+ {"headers,bodies,unknown,auth", 0, "unknown dump flag \"unknown\""},
} {
f := DumpFlags(-1)
initial := f
@@ -69,7 +69,7 @@ func TestDumpFlagsUnmarshallJSON(t *testing.T) {
{`"bodies,headers,auth"`, DumpBodies | DumpHeaders | DumpAuth, ""},
{`"bodies,headers,auth"`, DumpBodies | DumpHeaders | DumpAuth, ""},
{`"headers,bodies,requests,responses,auth,filters"`, DumpHeaders | DumpBodies | DumpRequests | DumpResponses | DumpAuth | DumpFilters, ""},
- {`"headers,bodies,unknown,auth"`, 0, "Unknown dump flag \"unknown\""},
+ {`"headers,bodies,unknown,auth"`, 0, "unknown dump flag \"unknown\""},
{`0`, DumpFlags(0), ""},
{strconv.Itoa(int(DumpBodies)), DumpBodies, ""},
{strconv.Itoa(int(DumpBodies | DumpHeaders | DumpAuth)), DumpBodies | DumpHeaders | DumpAuth, ""},
diff --git a/fs/filter/filter.go b/fs/filter/filter.go
index e35018fa50112..741580fffec9e 100644
--- a/fs/filter/filter.go
+++ b/fs/filter/filter.go
@@ -199,7 +199,7 @@ func NewFilter(opt *Opt) (f *Filter, err error) {
for _, rule := range f.Opt.FilesFrom {
if !inActive {
- return nil, fmt.Errorf("The usage of --files-from overrides all other filters, it should be used alone or with --files-from-raw")
+ return nil, fmt.Errorf("the usage of --files-from overrides all other filters, it should be used alone or with --files-from-raw")
}
f.initAddFile() // init to show --files-from set even if no files within
err := forEachLine(rule, false, func(line string) error {
@@ -214,7 +214,7 @@ func NewFilter(opt *Opt) (f *Filter, err error) {
// --files-from-raw can be used with --files-from, hence we do
// not need to get the value of f.InActive again
if !inActive {
- return nil, fmt.Errorf("The usage of --files-from-raw overrides all other filters, it should be used alone or with --files-from")
+ return nil, fmt.Errorf("the usage of --files-from-raw overrides all other filters, it should be used alone or with --files-from")
}
f.initAddFile() // init to show --files-from set even if no files within
err := forEachLine(rule, true, func(line string) error {
diff --git a/fs/filter/filter_test.go b/fs/filter/filter_test.go
index 81a01af3d1dac..27da120c4e5e1 100644
--- a/fs/filter/filter_test.go
+++ b/fs/filter/filter_test.go
@@ -62,7 +62,7 @@ func TestNewFilterForbiddenMixOfFilesFromAndFilterRule(t *testing.T) {
_, err := NewFilter(&Opt)
require.Error(t, err)
- require.Contains(t, err.Error(), "The usage of --files-from overrides all other filters")
+ require.Contains(t, err.Error(), "the usage of --files-from overrides all other filters")
}
func TestNewFilterForbiddenMixOfFilesFromRawAndFilterRule(t *testing.T) {
@@ -85,7 +85,7 @@ func TestNewFilterForbiddenMixOfFilesFromRawAndFilterRule(t *testing.T) {
_, err := NewFilter(&Opt)
require.Error(t, err)
- require.Contains(t, err.Error(), "The usage of --files-from-raw overrides all other filters")
+ require.Contains(t, err.Error(), "the usage of --files-from-raw overrides all other filters")
}
func TestNewFilterWithFilesFromAlone(t *testing.T) {
diff --git a/fs/hash/hash.go b/fs/hash/hash.go
index 48ecdcd4cb8af..93b1de3b22b03 100644
--- a/fs/hash/hash.go
+++ b/fs/hash/hash.go
@@ -153,7 +153,7 @@ func (h *Type) Set(s string) error {
*h = hash.hashType
return nil
}
- return fmt.Errorf("Unknown hash type %q", s)
+ return fmt.Errorf("unknown hash type %q", s)
}
// Type of the value
diff --git a/fs/log.go b/fs/log.go
index 423352344f7a4..8328b9106e01d 100644
--- a/fs/log.go
+++ b/fs/log.go
@@ -61,7 +61,7 @@ func (l *LogLevel) Set(s string) error {
return nil
}
}
- return fmt.Errorf("Unknown log level %q", s)
+ return fmt.Errorf("unknown log level %q", s)
}
// Type of the value
@@ -73,7 +73,7 @@ func (l *LogLevel) Type() string {
func (l *LogLevel) UnmarshalJSON(in []byte) error {
return UnmarshalJSONFlag(in, l, func(i int64) error {
if i < 0 || i >= int64(LogLevel(len(logLevelToString))) {
- return fmt.Errorf("Unknown log level %d", i)
+ return fmt.Errorf("unknown log level %d", i)
}
*l = (LogLevel)(i)
return nil
diff --git a/fs/open_options.go b/fs/open_options.go
index 938e38d04fd6f..e357a9a6990d0 100644
--- a/fs/open_options.go
+++ b/fs/open_options.go
@@ -72,28 +72,28 @@ func (o *RangeOption) Header() (key string, value string) {
func ParseRangeOption(s string) (po *RangeOption, err error) {
const preamble = "bytes="
if !strings.HasPrefix(s, preamble) {
- return nil, errors.New("Range: header invalid: doesn't start with " + preamble)
+ return nil, errors.New("range: header invalid: doesn't start with " + preamble)
}
s = s[len(preamble):]
if strings.ContainsRune(s, ',') {
- return nil, errors.New("Range: header invalid: contains multiple ranges which isn't supported")
+ return nil, errors.New("range: header invalid: contains multiple ranges which isn't supported")
}
dash := strings.IndexRune(s, '-')
if dash < 0 {
- return nil, errors.New("Range: header invalid: contains no '-'")
+ return nil, errors.New("range: header invalid: contains no '-'")
}
start, end := strings.TrimSpace(s[:dash]), strings.TrimSpace(s[dash+1:])
o := RangeOption{Start: -1, End: -1}
if start != "" {
o.Start, err = strconv.ParseInt(start, 10, 64)
if err != nil || o.Start < 0 {
- return nil, errors.New("Range: header invalid: bad start")
+ return nil, errors.New("range: header invalid: bad start")
}
}
if end != "" {
o.End, err = strconv.ParseInt(end, 10, 64)
if err != nil || o.End < 0 {
- return nil, errors.New("Range: header invalid: bad end")
+ return nil, errors.New("range: header invalid: bad end")
}
}
return &o, nil
diff --git a/fs/operations/check.go b/fs/operations/check.go
index a7a52e299b28d..5cb3b0cf998b3 100644
--- a/fs/operations/check.go
+++ b/fs/operations/check.go
@@ -80,7 +80,7 @@ func (c *checkMarch) DstOnly(dst fs.DirEntry) (recurse bool) {
if c.opt.OneWay {
return false
}
- err := fmt.Errorf("File not in %v", c.opt.Fsrc)
+ err := fmt.Errorf("file not in %v", c.opt.Fsrc)
fs.Errorf(dst, "%v", err)
_ = fs.CountError(err)
atomic.AddInt32(&c.differences, 1)
@@ -102,7 +102,7 @@ func (c *checkMarch) DstOnly(dst fs.DirEntry) (recurse bool) {
func (c *checkMarch) SrcOnly(src fs.DirEntry) (recurse bool) {
switch src.(type) {
case fs.Object:
- err := fmt.Errorf("File not in %v", c.opt.Fdst)
+ err := fmt.Errorf("file not in %v", c.opt.Fdst)
fs.Errorf(src, "%v", err)
_ = fs.CountError(err)
atomic.AddInt32(&c.differences, 1)
@@ -125,7 +125,7 @@ func (c *checkMarch) checkIdentical(ctx context.Context, dst, src fs.Object) (di
tr.Done(ctx, err)
}()
if sizeDiffers(ctx, src, dst) {
- err = fmt.Errorf("Sizes differ")
+ err = fmt.Errorf("sizes differ")
fs.Errorf(src, "%v", err)
return true, false, nil
}
@@ -424,7 +424,7 @@ func CheckSum(ctx context.Context, fsrc, fsum fs.Fs, sumFile string, hashType ha
continue
}
// filesystem missed the file, sum wasn't consumed
- err := fmt.Errorf("File not in %v", opt.Fdst)
+ err := fmt.Errorf("file not in %v", opt.Fdst)
fs.Errorf(filename, "%v", err)
_ = fs.CountError(err)
if lastErr == nil {
diff --git a/fs/operations/lsjson.go b/fs/operations/lsjson.go
index 5af5a4295808b..144a47bb6e13b 100644
--- a/fs/operations/lsjson.go
+++ b/fs/operations/lsjson.go
@@ -122,7 +122,7 @@ func newListJSON(ctx context.Context, fsrc fs.Fs, remote string, opt *ListJSONOp
return nil, fmt.Errorf("ListJSON failed to load config for crypt remote: %w", err)
}
if fsInfo.Name != "crypt" {
- return nil, errors.New("The remote needs to be of type \"crypt\"")
+ return nil, errors.New("the remote needs to be of type \"crypt\"")
}
lj.cipher, err = crypt.NewCipher(config)
if err != nil {
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index 5eccc44f719eb..bdeeaf03686e3 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -1407,7 +1407,7 @@ func Rcat(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser,
fs.Debugf(fdst, "Target remote doesn't support streaming uploads, creating temporary local FS to spool file")
tmpLocalFs, err := fs.TemporaryLocalFs(ctx)
if err != nil {
- return nil, fmt.Errorf("Failed to create temporary local FS to spool file: %w", err)
+ return nil, fmt.Errorf("failed to create temporary local FS to spool file: %w", err)
}
defer func() {
err := Purge(ctx, tmpLocalFs, "")
@@ -1523,7 +1523,7 @@ func GetCompareDest(ctx context.Context) (CompareDest []fs.Fs, err error) {
ci := fs.GetConfig(ctx)
CompareDest, err = cache.GetArr(ctx, ci.CompareDest)
if err != nil {
- return nil, fserrors.FatalError(fmt.Errorf("Failed to make fs for --compare-dest %q: %v", ci.CompareDest, err))
+ return nil, fserrors.FatalError(fmt.Errorf("failed to make fs for --compare-dest %q: %w", ci.CompareDest, err))
}
return CompareDest, nil
}
@@ -1562,7 +1562,7 @@ func GetCopyDest(ctx context.Context, fdst fs.Fs) (CopyDest []fs.Fs, err error)
ci := fs.GetConfig(ctx)
CopyDest, err = cache.GetArr(ctx, ci.CopyDest)
if err != nil {
- return nil, fserrors.FatalError(fmt.Errorf("Failed to make fs for --copy-dest %q: %v", ci.CopyDest, err))
+ return nil, fserrors.FatalError(fmt.Errorf("failed to make fs for --copy-dest %q: %w", ci.CopyDest, err))
}
if !SameConfigArr(fdst, CopyDest) {
return nil, fserrors.FatalError(errors.New("parameter to --copy-dest has to be on the same remote as destination"))
@@ -1777,7 +1777,7 @@ func copyURLFn(ctx context.Context, dstFileName string, url string, autoFilename
_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
headerFilename := path.Base(strings.Replace(params["filename"], "\\", "/", -1))
if err != nil || headerFilename == "" {
- return fmt.Errorf("copyurl failed: filename not found in the Content-Dispoition header")
+ return fmt.Errorf("CopyURL failed: filename not found in the Content-Dispoition header")
}
fs.Debugf(headerFilename, "filename found in Content-Disposition header.")
return fn(ctx, headerFilename, resp.Body, resp.ContentLength, modTime)
@@ -1822,7 +1822,7 @@ func BackupDir(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, srcFileName string)
if ci.BackupDir != "" {
backupDir, err = cache.Get(ctx, ci.BackupDir)
if err != nil {
- return nil, fserrors.FatalError(fmt.Errorf("Failed to make fs for --backup-dir %q: %v", ci.BackupDir, err))
+ return nil, fserrors.FatalError(fmt.Errorf("failed to make fs for --backup-dir %q: %w", ci.BackupDir, err))
}
if !SameConfig(fdst, backupDir) {
return nil, fserrors.FatalError(errors.New("parameter to --backup-dir has to be on the same remote as destination"))
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 04b26e8b8819e..02a44d385cf3e 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -1696,7 +1696,7 @@ func TestCopyFileMaxTransfer(t *testing.T) {
accounting.Stats(ctx).ResetCounters()
err = operations.CopyFile(ctx, r.Fremote, r.Flocal, file2.Path, file2.Path)
require.NotNil(t, err, "Did not get expected max transfer limit error")
- assert.Contains(t, err.Error(), "Max transfer limit reached")
+ assert.Contains(t, err.Error(), "max transfer limit reached")
assert.True(t, fserrors.IsFatalError(err), fmt.Sprintf("Not fatal error: %v: %#v:", err, err))
r.CheckLocalItems(t, file1, file2, file3, file4)
r.CheckRemoteItems(t, file1)
@@ -1708,7 +1708,7 @@ func TestCopyFileMaxTransfer(t *testing.T) {
accounting.Stats(ctx).ResetCounters()
err = operations.CopyFile(ctx, r.Fremote, r.Flocal, file3.Path, file3.Path)
require.NotNil(t, err)
- assert.Contains(t, err.Error(), "Max transfer limit reached")
+ assert.Contains(t, err.Error(), "max transfer limit reached")
assert.True(t, fserrors.IsNoRetryError(err))
r.CheckLocalItems(t, file1, file2, file3, file4)
r.CheckRemoteItems(t, file1)
diff --git a/fs/rc/internal.go b/fs/rc/internal.go
index 10077ae098651..24e67eabb0a94 100644
--- a/fs/rc/internal.go
+++ b/fs/rc/internal.go
@@ -475,7 +475,7 @@ func rcRunCommand(ctx context.Context, in Params) (out Params, err error) {
cmd.Stdout = httpResponse
cmd.Stderr = httpResponse
} else {
- return nil, fmt.Errorf("Unknown returnType %q", returnType)
+ return nil, fmt.Errorf("unknown returnType %q", returnType)
}
err = cmd.Run()
diff --git a/fs/rc/rcserver/rcserver_test.go b/fs/rc/rcserver/rcserver_test.go
index f75cfe52f2042..34181b631ed0c 100644
--- a/fs/rc/rcserver/rcserver_test.go
+++ b/fs/rc/rcserver/rcserver_test.go
@@ -497,7 +497,7 @@ func TestRCWithAuth(t *testing.T) {
ContentType: "application/x-www-form-urlencoded",
Status: http.StatusInternalServerError,
Expected: `{
- "error": "Unknown returnType \"POTATO\"",
+ "error": "unknown returnType \"POTATO\"",
"input": {
"command": "version",
"returnType": "POTATO"
diff --git a/fs/rc/webgui/webgui.go b/fs/rc/webgui/webgui.go
index ea37382eea511..8153024d173de 100644
--- a/fs/rc/webgui/webgui.go
+++ b/fs/rc/webgui/webgui.go
@@ -63,7 +63,7 @@ func CheckAndDownloadWebGUIRelease(checkUpdate bool, forceUpdate bool, fetchURL
// Get the latest release details
WebUIURL, tag, size, err := GetLatestReleaseURL(fetchURL)
if err != nil {
- return fmt.Errorf("Error checking for web gui release update, skipping update: %w", err)
+ return fmt.Errorf("error checking for web gui release update, skipping update: %w", err)
}
dat, err := ioutil.ReadFile(tagPath)
tagsMatch := false
diff --git a/fs/sync/sync.go b/fs/sync/sync.go
index 58e74e22a6377..052b867e8d60c 100644
--- a/fs/sync/sync.go
+++ b/fs/sync/sync.go
@@ -825,7 +825,7 @@ func (s *syncCopyMove) tryRename(src fs.Object) bool {
// errorMaxDurationReached defines error when transfer duration is reached
// Used for checking on exit and matching to correct exit code.
-var errorMaxDurationReached = fserrors.FatalError(errors.New("Max transfer duration reached as set by --max-duration"))
+var errorMaxDurationReached = fserrors.FatalError(errors.New("max transfer duration reached as set by --max-duration"))
// Syncs fsrc into fdst
//
diff --git a/fstest/mockobject/mockobject.go b/fstest/mockobject/mockobject.go
index 0d0c4516cb049..084536b85a1a5 100644
--- a/fstest/mockobject/mockobject.go
+++ b/fstest/mockobject/mockobject.go
@@ -140,7 +140,7 @@ func (o *ContentMockObject) Open(ctx context.Context, options ...fs.OpenOption)
offset, limit = x.Decode(size)
default:
if option.Mandatory() {
- return nil, fmt.Errorf("Unsupported mandatory option: %v", option)
+ return nil, fmt.Errorf("unsupported mandatory option: %v", option)
}
}
}
diff --git a/fstest/test_all/clean.go b/fstest/test_all/clean.go
index c12a4839abd72..6da23b7e1cc46 100644
--- a/fstest/test_all/clean.go
+++ b/fstest/test_all/clean.go
@@ -56,7 +56,7 @@ func cleanFs(ctx context.Context, remote string, cleanup bool) error {
}
err = operations.Purge(ctx, dir, "")
if err != nil {
- err = fmt.Errorf("Purge failed: %w", err)
+ err = fmt.Errorf("purge failed: %w", err)
lastErr = err
fs.Errorf(dir, "%v", err)
return nil
diff --git a/lib/http/http.go b/lib/http/http.go
index ac5befd3719f4..1ff7c11489271 100644
--- a/lib/http/http.go
+++ b/lib/http/http.go
@@ -122,7 +122,7 @@ func useSSL(opt Options) bool {
func NewServer(listeners, tlsListeners []net.Listener, opt Options) (Server, error) {
// Validate input
if len(listeners) == 0 && len(tlsListeners) == 0 {
- return nil, errors.New("Can't create server without listeners")
+ return nil, errors.New("can't create server without listeners")
}
// Prepare TLS config
@@ -130,12 +130,12 @@ func NewServer(listeners, tlsListeners []net.Listener, opt Options) (Server, err
useSSL := useSSL(opt)
if (len(opt.SslCertBody) > 0) != (len(opt.SslKeyBody) > 0) {
- err := errors.New("Need both SslCertBody and SslKeyBody to use SSL")
+ err := errors.New("need both SslCertBody and SslKeyBody to use SSL")
log.Fatalf(err.Error())
return nil, err
}
if (opt.SslCert != "") != (opt.SslKey != "") {
- err := errors.New("Need both -cert and -key to use SSL")
+ err := errors.New("need both -cert and -key to use SSL")
log.Fatalf(err.Error())
return nil, err
}
@@ -156,12 +156,12 @@ func NewServer(listeners, tlsListeners []net.Listener, opt Options) (Server, err
Certificates: []tls.Certificate{cert},
}
} else if len(listeners) == 0 && len(tlsListeners) != 0 {
- return nil, errors.New("No SslKey or non-tlsListeners")
+ return nil, errors.New("no SslKey or non-tlsListeners")
}
if opt.ClientCA != "" {
if !useSSL {
- err := errors.New("Can't use --client-ca without --cert and --key")
+ err := errors.New("can't use --client-ca without --cert and --key")
log.Fatalf(err.Error())
return nil, err
}
@@ -172,7 +172,7 @@ func NewServer(listeners, tlsListeners []net.Listener, opt Options) (Server, err
return nil, err
}
if !certpool.AppendCertsFromPEM(pem) {
- err := errors.New("Can't parse client certificate authority")
+ err := errors.New("can't parse client certificate authority")
log.Fatalf(err.Error())
return nil, err
}
diff --git a/lib/jwtutil/jwtutil.go b/lib/jwtutil/jwtutil.go
index 8473c45bddbb6..af99ed3f80606 100644
--- a/lib/jwtutil/jwtutil.go
+++ b/lib/jwtutil/jwtutil.go
@@ -78,7 +78,7 @@ func Config(id, name string, claims *jws.ClaimSet, header *jws.Header, queryPara
result := &response{}
err = json.NewDecoder(strings.NewReader(s)).Decode(result)
if result.AccessToken == "" && err == nil {
- err = errors.New("No AccessToken in Response")
+ err = errors.New("no AccessToken in Response")
}
if err != nil {
return fmt.Errorf("jwtutil: failed to get token: %w", err)
diff --git a/lib/rest/url.go b/lib/rest/url.go
index 4a1d7139091eb..8f443d59d3a93 100644
--- a/lib/rest/url.go
+++ b/lib/rest/url.go
@@ -11,7 +11,7 @@ import (
func URLJoin(base *url.URL, path string) (*url.URL, error) {
rel, err := url.Parse(path)
if err != nil {
- return nil, fmt.Errorf("Error parsing %q as URL: %w", path, err)
+ return nil, fmt.Errorf("error parsing %q as URL: %w", path, err)
}
return base.ResolveReference(rel), nil
}
diff --git a/vfs/file.go b/vfs/file.go
index 4c4fc214ac2dd..2d3199f137dc2 100644
--- a/vfs/file.go
+++ b/vfs/file.go
@@ -400,7 +400,7 @@ func (f *File) _applyPendingModTime() error {
defer func() { f.pendingModTime = time.Time{} }()
if f.o == nil {
- return errors.New("Cannot apply ModTime, file object is not available")
+ return errors.New("cannot apply ModTime, file object is not available")
}
dt := f.pendingModTime.Sub(f.o.ModTime(context.Background()))
diff --git a/vfs/vfscommon/cachemode.go b/vfs/vfscommon/cachemode.go
index a7f35fe9c14db..a2d91bd4ea38b 100644
--- a/vfs/vfscommon/cachemode.go
+++ b/vfs/vfscommon/cachemode.go
@@ -40,7 +40,7 @@ func (l *CacheMode) Set(s string) error {
return nil
}
}
- return fmt.Errorf("Unknown cache mode level %q", s)
+ return fmt.Errorf("unknown cache mode level %q", s)
}
// Type of the value
@@ -52,7 +52,7 @@ func (l *CacheMode) Type() string {
func (l *CacheMode) UnmarshalJSON(in []byte) error {
return fs.UnmarshalJSONFlag(in, l, func(i int64) error {
if i < 0 || i >= int64(len(cacheModeToString)) {
- return fmt.Errorf("Unknown cache mode level %d", i)
+ return fmt.Errorf("unknown cache mode level %d", i)
}
*l = CacheMode(i)
return nil
diff --git a/vfs/vfsflags/filemode.go b/vfs/vfsflags/filemode.go
index 50a8bc4233c8e..b1c2e8bcd8e97 100644
--- a/vfs/vfsflags/filemode.go
+++ b/vfs/vfsflags/filemode.go
@@ -20,7 +20,7 @@ func (x *FileMode) String() string {
func (x *FileMode) Set(s string) error {
i, err := strconv.ParseInt(s, 8, 64)
if err != nil {
- return fmt.Errorf("Bad FileMode - must be octal digits: %w", err)
+ return fmt.Errorf("bad FileMode - must be octal digits: %w", err)
}
*x.Mode = (os.FileMode)(i)
return nil
From 517e7d927199f6bfe1ac8e7b06c5308bc2b7c022 Mon Sep 17 00:00:00 2001
From: buda
Date: Mon, 27 Jun 2022 20:56:03 +0400
Subject: [PATCH 108/560] accounting: fix unknown length file transfers count 3
transfers each #6213
This was caused by nested calls to NewTransfer/Done.
This fixes the problem by only incrementing transfers if the remote is
present in the transferMap which means we only increment it once.
---
fs/accounting/stats.go | 6 +++---
fs/accounting/transfermap.go | 5 ++++-
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/fs/accounting/stats.go b/fs/accounting/stats.go
index b4b32a8eecec2..fc17f95ac23ca 100644
--- a/fs/accounting/stats.go
+++ b/fs/accounting/stats.go
@@ -714,10 +714,10 @@ func (s *StatsInfo) NewTransferRemoteSize(remote string, size int64) *Transfer {
// DoneTransferring removes a transfer from the stats
//
-// if ok is true then it increments the transfers count
+// if ok is true and it was in the transfermap (to avoid incrementing in case of nested calls, #6213) then it increments the transfers count
func (s *StatsInfo) DoneTransferring(remote string, ok bool) {
- s.transferring.del(remote)
- if ok {
+ existed := s.transferring.del(remote)
+ if ok && existed {
s.mu.Lock()
s.transfers++
s.mu.Unlock()
diff --git a/fs/accounting/transfermap.go b/fs/accounting/transfermap.go
index ed64bf3697408..4fb30a07980b5 100644
--- a/fs/accounting/transfermap.go
+++ b/fs/accounting/transfermap.go
@@ -34,10 +34,13 @@ func (tm *transferMap) add(tr *Transfer) {
}
// del removes a transfer from the map by name
-func (tm *transferMap) del(remote string) {
+func (tm *transferMap) del(remote string) bool {
tm.mu.Lock()
+ _, exists := tm.items[remote]
delete(tm.items, remote)
tm.mu.Unlock()
+
+ return exists
}
// merge adds items from another map
From 32006033e6d576630207514cbe3f4779d65133bd Mon Sep 17 00:00:00 2001
From: mirekphd <36706320+mirekphd@users.noreply.github.com>
Date: Mon, 27 Jun 2022 20:33:07 +0200
Subject: [PATCH 109/560] docs: note wider impact of --checkers=N on
parallelism #6280
---
docs/content/docs.md | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/docs/content/docs.md b/docs/content/docs.md
index 50bc83cfae7a0..66777e1b8981d 100644
--- a/docs/content/docs.md
+++ b/docs/content/docs.md
@@ -639,12 +639,22 @@ objects to transfer is held in memory before the transfers start.
### --checkers=N ###
-The number of checkers to run in parallel. Checkers do the equality
-checking of files during a sync. For some storage systems (e.g. S3,
-Swift, Dropbox) this can take a significant amount of time so they are
-run in parallel.
-
-The default is to run 8 checkers in parallel.
+Originally controlling just the number of file checkers to run in parallel,
+e.g. by `rclone copy`. Now a fairly universal parallelism control
+used by `rclone` in several places.
+
+Note: checkers do the equality checking of files during a sync.
+For some storage systems (e.g. S3, Swift, Dropbox) this can take
+a significant amount of time so they are run in parallel.
+
+The default is to run 8 checkers in parallel. However, in case
+of slow-reacting backends you may need to lower (rather than increase)
+this default by setting `--checkers` to 4 or less threads. This is
+especially advised if you are experiencing backend server crashes
+during file checking phase (e.g. on subsequent or top-up backups
+where little or no file copying is done and checking takes up
+most of the time). Increase this setting only with utmost care,
+while monitoring your server health and file checking throughput.
### -c, --checksum ###
From 326c43ab3f5c7c8570bf87b4656b41846ce00877 Mon Sep 17 00:00:00 2001
From: vyloy
Date: Sat, 18 Jun 2022 15:29:21 +0800
Subject: [PATCH 110/560] s3: add IDrive e2 to provider list
---
backend/s3/s3.go | 55 +++++++++++++++++--
docs/content/_index.md | 1 +
docs/content/s3.md | 121 +++++++++++++++++++++++++++++++++++++++--
3 files changed, 169 insertions(+), 8 deletions(-)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index c81024ce3c60c..c5dfbcb74c25d 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -8,6 +8,7 @@ import (
"crypto/tls"
"encoding/base64"
"encoding/hex"
+ "encoding/json"
"encoding/xml"
"errors"
"fmt"
@@ -60,9 +61,16 @@ import (
func init() {
fs.Register(&fs.RegInfo{
Name: "s3",
- Description: "Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi",
+ Description: "Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, IDrive e2, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi",
NewFs: NewFs,
CommandHelp: commandHelp,
+ Config: func(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
+ switch config.State {
+ case "":
+ return nil, setEndpointValueForIDriveE2(m)
+ }
+ return nil, fmt.Errorf("unknown state %q", config.State)
+ },
Options: []fs.Option{{
Name: fs.ConfigProvider,
Help: "Choose your S3 provider.",
@@ -98,6 +106,9 @@ func init() {
}, {
Value: "IBMCOS",
Help: "IBM COS S3",
+ }, {
+ Value: "IDrive",
+ Help: "IDrive e2",
}, {
Value: "LyveCloud",
Help: "Seagate Lyve Cloud",
@@ -369,7 +380,7 @@ func init() {
}, {
Name: "region",
Help: "Region to connect to.\n\nLeave blank if you are using an S3 clone and you don't have a region.",
- Provider: "!AWS,Alibaba,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS,HuaweiOBS",
+ Provider: "!AWS,Alibaba,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS,HuaweiOBS,IDrive",
Examples: []fs.OptionExample{{
Value: "",
Help: "Use this if unsure.\nWill use v4 signatures and an empty region.",
@@ -983,7 +994,7 @@ func init() {
}, {
Name: "endpoint",
Help: "Endpoint for S3 API.\n\nRequired when using an S3 clone.",
- Provider: "!AWS,IBMCOS,TencentCOS,HuaweiOBS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp",
+ Provider: "!AWS,IBMCOS,IDrive,TencentCOS,HuaweiOBS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp",
Examples: []fs.OptionExample{{
Value: "objects-us-east-1.dream.io",
Help: "Dream Objects endpoint",
@@ -1393,7 +1404,7 @@ func init() {
}, {
Name: "location_constraint",
Help: "Location constraint - must be set to match the Region.\n\nLeave blank if not sure. Used when creating buckets only.",
- Provider: "!AWS,IBMCOS,Alibaba,HuaweiOBS,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS",
+ Provider: "!AWS,IBMCOS,IDrive,Alibaba,HuaweiOBS,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS",
}, {
Name: "acl",
Help: `Canned ACL used when creating buckets and storing or copying objects.
@@ -2329,6 +2340,37 @@ func (f *Fs) setUploadCutoff(cs fs.SizeSuffix) (old fs.SizeSuffix, err error) {
return
}
+// setEndpointValueForIDriveE2 gets user region endpoint against the Access Key details by calling the API
+func setEndpointValueForIDriveE2(m configmap.Mapper) (err error) {
+ value, ok := m.Get(fs.ConfigProvider)
+ if !ok || value != "IDrive" {
+ return
+ }
+ value, ok = m.Get("access_key_id")
+ if !ok || value == "" {
+ return
+ }
+ client := &http.Client{Timeout: time.Second * 3}
+ // API to get user region endpoint against the Access Key details: https://www.idrive.com/e2/guides/get_region_endpoint
+ resp, err := client.Post("https://api.idrivee2.com/api/service/get_region_end_point",
+ "application/json",
+ strings.NewReader(`{"access_key": "`+value+`"}`))
+ if err != nil {
+ return
+ }
+ defer fs.CheckClose(resp.Body, &err)
+ decoder := json.NewDecoder(resp.Body)
+ var data = &struct {
+ RespCode int `json:"resp_code"`
+ RespMsg string `json:"resp_msg"`
+ DomainName string `json:"domain_name"`
+ }{}
+ if err = decoder.Decode(data); err == nil && data.RespCode == 0 {
+ m.Set("endpoint", data.DomainName)
+ }
+ return
+}
+
// Set the provider quirks
//
// There should be no testing against opt.Provider anywhere in the
@@ -2375,6 +2417,8 @@ func setQuirks(opt *Options) {
virtualHostStyle = false
urlEncodeListings = false
useMultipartEtag = false // untested
+ case "IDrive":
+ virtualHostStyle = false
case "LyveCloud":
useMultipartEtag = false // LyveCloud seems to calculate multipart Etags differently from AWS
case "Minio":
@@ -2554,6 +2598,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
f.features.SetTier = false
f.features.GetTier = false
}
+ if opt.Provider == "IDrive" {
+ f.features.SetTier = false
+ }
// f.listMultipartUploads()
return f, nil
}
diff --git a/docs/content/_index.md b/docs/content/_index.md
index dcf51e23f8c9c..58410d6d4330d 100644
--- a/docs/content/_index.md
+++ b/docs/content/_index.md
@@ -132,6 +132,7 @@ WebDAV or S3, that work out of the box.)
{{< provider name="Internet Archive" home="https://archive.org/" config="/internetarchive/" >}}
{{< provider name="Jottacloud" home="https://www.jottacloud.com/en/" config="/jottacloud/" >}}
{{< provider name="IBM COS S3" home="http://www.ibm.com/cloud/object-storage" config="/s3/#ibm-cos-s3" >}}
+{{< provider name="IDrive e2" home="https://www.idrive.com/e2/" config="/s3/#idrive-e2" >}}
{{< provider name="Koofr" home="https://koofr.eu/" config="/koofr/" >}}
{{< provider name="Mail.ru Cloud" home="https://cloud.mail.ru/" config="/mailru/" >}}
{{< provider name="Memset Memstore" home="https://www.memset.com/cloud/storage/" config="/swift/" >}}
diff --git a/docs/content/s3.md b/docs/content/s3.md
index fbd47c76327cd..c2adbfe95222c 100644
--- a/docs/content/s3.md
+++ b/docs/content/s3.md
@@ -18,6 +18,7 @@ The S3 backend can be used with a number of different providers:
{{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}}
{{< provider name="Huawei OBS" home="https://www.huaweicloud.com/intl/en-us/product/obs.html" config="/s3/#huawei-obs" >}}
{{< provider name="IBM COS S3" home="http://www.ibm.com/cloud/object-storage" config="/s3/#ibm-cos-s3" >}}
+{{< provider name="IDrive e2" home="https://www.idrive.com/e2/" config="/s3/#idrive-e2" >}}
{{< provider name="Minio" home="https://www.minio.io/" config="/s3/#minio" >}}
{{< provider name="RackCorp Object Storage" home="https://www.rackcorp.com/" config="/s3/#RackCorp" >}}
{{< provider name="Scaleway" home="https://www.scaleway.com/en/object-storage/" config="/s3/#scaleway" >}}
@@ -570,7 +571,7 @@ A simple solution is to set the `--s3-upload-cutoff 0` and force all the files t
{{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/s3/s3.go then run make backenddocs" >}}
### Standard options
-Here are the standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
+Here are the standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, IDrive e2, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
#### --s3-provider
@@ -601,6 +602,8 @@ Properties:
- Huawei Object Storage Service
- "IBMCOS"
- IBM COS S3
+ - "IDrive"
+ - IDrive e2
- "LyveCloud"
- Seagate Lyve Cloud
- "Minio"
@@ -836,7 +839,7 @@ Properties:
- Config: region
- Env Var: RCLONE_S3_REGION
-- Provider: !AWS,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS,HuaweiOBS
+- Provider: !AWS,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS,HuaweiOBS,IDrive
- Type: string
- Required: false
- Examples:
@@ -1362,7 +1365,7 @@ Properties:
- Config: endpoint
- Env Var: RCLONE_S3_ENDPOINT
-- Provider: !AWS,IBMCOS,TencentCOS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp,HuaweiOBS
+- Provider: !AWS,IBMCOS,IDrive,TencentCOS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp,HuaweiOBS
- Type: string
- Required: false
- Examples:
@@ -1601,7 +1604,7 @@ Properties:
- Config: location_constraint
- Env Var: RCLONE_S3_LOCATION_CONSTRAINT
-- Provider: !AWS,IBMCOS,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS,HuaweiOBS
+- Provider: !AWS,IBMCOS,IDrive,Alibaba,ChinaMobile,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS,HuaweiOBS
- Type: string
- Required: false
@@ -3076,6 +3079,116 @@ acl> 1
rclone delete IBM-COS-XREGION:newbucket/file.txt
```
+### IDrive e2 {#idrive-e2}
+
+Here is an example of making an [IDrive e2](https://www.idrive.com/e2/)
+configuration. First run:
+
+ rclone config
+
+This will guide you through an interactive setup process.
+
+```
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+
+Enter name for new remote.
+name> e2
+
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+[snip]
+XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, IDrive e2, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi
+ \ (s3)
+[snip]
+Storage> s3
+
+Option provider.
+Choose your S3 provider.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+[snip]
+XX / IDrive e2
+ \ (IDrive)
+[snip]
+provider> IDrive
+
+Option env_auth.
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
+Only applies if access_key_id and secret_access_key is blank.
+Choose a number from below, or type in your own boolean value (true or false).
+Press Enter for the default (false).
+ 1 / Enter AWS credentials in the next step.
+ \ (false)
+ 2 / Get AWS credentials from the environment (env vars or IAM).
+ \ (true)
+env_auth>
+
+Option access_key_id.
+AWS Access Key ID.
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+access_key_id> YOUR_ACCESS_KEY
+
+Option secret_access_key.
+AWS Secret Access Key (password).
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+secret_access_key> YOUR_SECRET_KEY
+
+Option acl.
+Canned ACL used when creating buckets and storing or copying objects.
+This ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.
+For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+Note that this ACL is applied when server-side copying objects as S3
+doesn't copy the ACL from the source but rather writes a fresh one.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ / Owner gets FULL_CONTROL.
+ 1 | No one else has access rights (default).
+ \ (private)
+ / Owner gets FULL_CONTROL.
+ 2 | The AllUsers group gets READ access.
+ \ (public-read)
+ / Owner gets FULL_CONTROL.
+ 3 | The AllUsers group gets READ and WRITE access.
+ | Granting this on a bucket is generally not recommended.
+ \ (public-read-write)
+ / Owner gets FULL_CONTROL.
+ 4 | The AuthenticatedUsers group gets READ access.
+ \ (authenticated-read)
+ / Object owner gets FULL_CONTROL.
+ 5 | Bucket owner gets READ access.
+ | If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
+ \ (bucket-owner-read)
+ / Both the object owner and the bucket owner get FULL_CONTROL over the object.
+ 6 | If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
+ \ (bucket-owner-full-control)
+acl>
+
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n>
+
+Configuration complete.
+Options:
+- type: s3
+- provider: IDrive
+- access_key_id: YOUR_ACCESS_KEY
+- secret_access_key: YOUR_SECRET_KEY
+- endpoint: q9d9.la12.idrivee2-5.com
+Keep this "e2" remote?
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+```
+
### Minio
[Minio](https://minio.io/) is an object storage server built for cloud application developers and devops.
From 5de9278650584ddcb757fd82d28fdcede3927752 Mon Sep 17 00:00:00 2001
From: Martin Czygan <53705+miku@users.noreply.github.com>
Date: Tue, 28 Jun 2022 13:51:59 +0200
Subject: [PATCH 111/560] fs/cache: make sure we call the Shutdown method on
backends
This change ensures we call the Shutdown method on backends when
they drop out of the fs/cache and at program exit.
Some backends implement the optional fs.Shutdowner interface. Until now,
Shutdown is only checked and called, when a backend is wrapped (e.g.
crypt, compress, ...).
To have a general way to perform operations at the end of the backend
lifecycle with proper error handling, we can call Shutdown at cache
clear time.
We add a finalize hook to the cache which will be called when values
drop out of the cache.
Previous discussion: https://forum.rclone.org/t/31336
---
cmd/cmd.go | 6 ++++++
fs/cache/cache.go | 5 +++++
lib/cache/cache.go | 36 +++++++++++++++++++++++++++++-------
lib/cache/cache_test.go | 34 ++++++++++++++++++++++++++++++++++
4 files changed, 74 insertions(+), 7 deletions(-)
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 5dd120be7fd6a..6209fad55d949 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -321,6 +321,12 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
}
}
+ // clear cache and shutdown backends
+ cache.Clear()
+ if lastErr := accounting.GlobalStats().GetLastError(); cmdErr == nil {
+ cmdErr = lastErr
+ }
+
// Log the final error message and exit
if cmdErr != nil {
nerrs := accounting.GlobalStats().GetErrors()
diff --git a/fs/cache/cache.go b/fs/cache/cache.go
index 3bdc8d37fc1b5..4e2c4638e9924 100644
--- a/fs/cache/cache.go
+++ b/fs/cache/cache.go
@@ -25,6 +25,11 @@ func createOnFirstUse() {
c = cache.New()
c.SetExpireDuration(ci.FsCacheExpireDuration)
c.SetExpireInterval(ci.FsCacheExpireInterval)
+ c.SetFinalizer(func(value interface{}) {
+ if s, ok := value.(fs.Shutdowner); ok {
+ _ = fs.CountError(s.Shutdown(context.Background()))
+ }
+ })
})
}
diff --git a/lib/cache/cache.go b/lib/cache/cache.go
index f9533c13718ca..b8d323ff1276a 100644
--- a/lib/cache/cache.go
+++ b/lib/cache/cache.go
@@ -16,6 +16,7 @@ type Cache struct {
expireRunning bool
expireDuration time.Duration // expire the cache entry when it is older than this
expireInterval time.Duration // interval to run the cache expire
+ finalize func(value interface{})
}
// New creates a new cache with the default expire duration and interval
@@ -25,6 +26,7 @@ func New() *Cache {
expireRunning: false,
expireDuration: 300 * time.Second,
expireInterval: 60 * time.Second,
+ finalize: func(_ interface{}) {},
}
}
@@ -154,7 +156,10 @@ func (c *Cache) GetMaybe(key string) (value interface{}, found bool) {
// Returns true if the entry was found
func (c *Cache) Delete(key string) bool {
c.mu.Lock()
- _, found := c.cache[key]
+ entry, found := c.cache[key]
+ if found {
+ c.finalize(entry.value)
+ }
delete(c.cache, key)
c.mu.Unlock()
return found
@@ -165,11 +170,13 @@ func (c *Cache) Delete(key string) bool {
// Returns number of entries deleted
func (c *Cache) DeletePrefix(prefix string) (deleted int) {
c.mu.Lock()
- for k := range c.cache {
- if strings.HasPrefix(k, prefix) {
- delete(c.cache, k)
- deleted++
+ for key, entry := range c.cache {
+ if !strings.HasPrefix(key, prefix) {
+ continue
}
+ c.finalize(entry.value)
+ delete(c.cache, key)
+ deleted++
}
c.mu.Unlock()
return deleted
@@ -183,12 +190,17 @@ func (c *Cache) Rename(oldKey, newKey string) (value interface{}, found bool) {
c.mu.Lock()
if newEntry, newFound := c.cache[newKey]; newFound {
// If new entry is found use that
+ if _, oldFound := c.cache[oldKey]; oldFound {
+ // If there's an old entry, we drop it and also try shutdown.
+ c.finalize(c.cache[oldKey].value)
+ }
delete(c.cache, oldKey)
value, found = newEntry.value, newFound
c.used(newEntry)
} else if oldEntry, oldFound := c.cache[oldKey]; oldFound {
// If old entry is found rename it to new and use that
c.cache[newKey] = oldEntry
+ // No need to shutdown here, as value lives on under newKey
delete(c.cache, oldKey)
c.used(oldEntry)
value, found = oldEntry.value, oldFound
@@ -204,6 +216,7 @@ func (c *Cache) cacheExpire() {
now := time.Now()
for key, entry := range c.cache {
if entry.pinCount <= 0 && now.Sub(entry.lastUsed) > c.expireDuration {
+ c.finalize(entry.value)
delete(c.cache, key)
}
}
@@ -218,10 +231,12 @@ func (c *Cache) cacheExpire() {
// Clear removes everything from the cache
func (c *Cache) Clear() {
c.mu.Lock()
- for k := range c.cache {
- delete(c.cache, k)
+ for key, entry := range c.cache {
+ c.finalize(entry.value)
+ delete(c.cache, key)
}
c.mu.Unlock()
+ return
}
// Entries returns the number of entries in the cache
@@ -231,3 +246,10 @@ func (c *Cache) Entries() int {
c.mu.Unlock()
return entries
}
+
+// SetFinalizer sets a function to be called when a value drops out of the cache
+func (c *Cache) SetFinalizer(finalize func(interface{})) {
+ c.mu.Lock()
+ c.finalize = finalize
+ c.mu.Unlock()
+}
diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go
index 089db07ef618e..415e167c3f1c1 100644
--- a/lib/cache/cache_test.go
+++ b/lib/cache/cache_test.go
@@ -333,3 +333,37 @@ func TestCacheRename(t *testing.T) {
assert.Equal(t, 1, c.Entries())
}
+
+func TestCacheFinalize(t *testing.T) {
+ c := New()
+ numCalled := 0
+ c.SetFinalizer(func(v interface{}) {
+ numCalled++
+ })
+ create := func(path string) (interface{}, bool, error) {
+ return path, true, nil
+ }
+ _, _ = c.Get("ok", create)
+ assert.Equal(t, 0, numCalled)
+ c.Clear()
+ assert.Equal(t, 1, numCalled)
+
+ _, _ = c.Get("ok", create)
+ c.Delete("ok")
+ assert.Equal(t, 2, numCalled)
+
+ _, _ = c.Get("ok", create)
+ c.DeletePrefix("ok")
+ assert.Equal(t, 3, numCalled)
+
+ _, _ = c.Get("old", create)
+ _, _ = c.Get("new", create)
+ c.Rename("old", "new")
+ assert.Equal(t, 4, numCalled)
+
+ c.expireDuration = 1 * time.Millisecond
+ _, _ = c.Get("ok", create)
+ time.Sleep(2 * time.Millisecond)
+ c.cacheExpire() // "ok" and "new" fall out of cache
+ assert.Equal(t, 6, numCalled)
+}
From 0fca4d2c864732ddb6ba682f258c5bc29d317edf Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 10:55:03 +0100
Subject: [PATCH 112/560] Add buda to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 90ea4b9fb8cf2..7b7be2b84cdcc 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -617,3 +617,4 @@ put them back in again.` >}}
* Caleb
* J-P Treen
* Martin Czygan <53705+miku@users.noreply.github.com>
+ * buda
From 370c8fa220933b05c3e8479f4009c2ecf8c46269 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 10:55:03 +0100
Subject: [PATCH 113/560] Add mirekphd to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 7b7be2b84cdcc..9b8f3ae6cfa3a 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -618,3 +618,4 @@ put them back in again.` >}}
* J-P Treen
* Martin Czygan <53705+miku@users.noreply.github.com>
* buda
+ * mirekphd <36706320+mirekphd@users.noreply.github.com>
From 35f24d5b84f9c4e98795e52d0b46fa85e22744ae Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 10:55:03 +0100
Subject: [PATCH 114/560] Add vyloy to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 9b8f3ae6cfa3a..66f33c154de12 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -619,3 +619,4 @@ put them back in again.` >}}
* Martin Czygan <53705+miku@users.noreply.github.com>
* buda
* mirekphd <36706320+mirekphd@users.noreply.github.com>
+ * vyloy
From 461d041c4df26f290329864b295a2c2ee7fee1fd Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 27 Jun 2022 12:29:13 +0100
Subject: [PATCH 115/560] fstest: remove spurious contents return from
PutTestContents and friends
---
backend/chunker/chunker_internal_test.go | 14 +++++++-------
backend/drive/drive_internal_test.go | 6 +++---
backend/hasher/hasher_internal_test.go | 2 +-
backend/union/union_internal_test.go | 6 +++---
fs/operations/operations_test.go | 2 +-
fstest/fstests/fstests.go | 12 +++++++-----
6 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/backend/chunker/chunker_internal_test.go b/backend/chunker/chunker_internal_test.go
index 9299305dc39ba..c875c6762e5ba 100644
--- a/backend/chunker/chunker_internal_test.go
+++ b/backend/chunker/chunker_internal_test.go
@@ -59,7 +59,7 @@ var mtime1 = fstest.Time("2001-02-03T04:05:06.499999999Z")
func testPutFile(ctx context.Context, t *testing.T, f fs.Fs, name, contents, message string, check bool) fs.Object {
item := fstest.Item{Path: name, ModTime: mtime1}
- _, obj := fstests.PutTestContents(ctx, t, f, &item, contents, check)
+ obj := fstests.PutTestContents(ctx, t, f, &item, contents, check)
assert.NotNil(t, obj, message)
return obj
}
@@ -440,7 +440,7 @@ func testSmallFileInternals(t *testing.T, f *Fs) {
checkSmallFile := func(name, contents string) {
filename := path.Join(dir, name)
item := fstest.Item{Path: filename, ModTime: modTime}
- _, put := fstests.PutTestContents(ctx, t, f, &item, contents, false)
+ put := fstests.PutTestContents(ctx, t, f, &item, contents, false)
assert.NotNil(t, put)
checkSmallFileInternals(put)
checkContents(put, contents)
@@ -489,7 +489,7 @@ func testPreventCorruption(t *testing.T, f *Fs) {
newFile := func(name string) fs.Object {
item := fstest.Item{Path: path.Join(dir, name), ModTime: modTime}
- _, obj := fstests.PutTestContents(ctx, t, f, &item, contents, true)
+ obj := fstests.PutTestContents(ctx, t, f, &item, contents, true)
require.NotNil(t, obj)
return obj
}
@@ -599,7 +599,7 @@ func testChunkNumberOverflow(t *testing.T, f *Fs) {
newFile := func(f fs.Fs, name string) (obj fs.Object, filename string, txnID string) {
filename = path.Join(dir, name)
item := fstest.Item{Path: filename, ModTime: modTime}
- _, obj = fstests.PutTestContents(ctx, t, f, &item, contents, true)
+ obj = fstests.PutTestContents(ctx, t, f, &item, contents, true)
require.NotNil(t, obj)
if chunkObj, isChunkObj := obj.(*Object); isChunkObj {
txnID = chunkObj.xactID
@@ -716,7 +716,7 @@ func testFutureProof(t *testing.T, f *Fs) {
name = f.makeChunkName(name, part-1, "", "")
}
item := fstest.Item{Path: name, ModTime: modTime}
- _, obj := fstests.PutTestContents(ctx, t, f.base, &item, data, true)
+ obj := fstests.PutTestContents(ctx, t, f.base, &item, data, true)
assert.NotNil(t, obj, msg)
}
@@ -790,7 +790,7 @@ func testBackwardsCompatibility(t *testing.T, f *Fs) {
newFile := func(f fs.Fs, name string) (fs.Object, string) {
filename := path.Join(dir, name)
item := fstest.Item{Path: filename, ModTime: modTime}
- _, obj := fstests.PutTestContents(ctx, t, f, &item, contents, true)
+ obj := fstests.PutTestContents(ctx, t, f, &item, contents, true)
require.NotNil(t, obj)
return obj, filename
}
@@ -844,7 +844,7 @@ func testChunkerServerSideMove(t *testing.T, f *Fs) {
modTime := fstest.Time("2001-02-03T04:05:06.499999999Z")
item := fstest.Item{Path: "movefile", ModTime: modTime}
contents := "abcdef"
- _, file := fstests.PutTestContents(ctx, t, fs1, &item, contents, true)
+ file := fstests.PutTestContents(ctx, t, fs1, &item, contents, true)
dstOverwritten, _ := fs2.NewObject(ctx, "movefile")
dstFile, err := operations.Move(ctx, fs2, dstOverwritten, "movefile", file)
diff --git a/backend/drive/drive_internal_test.go b/backend/drive/drive_internal_test.go
index 2a21fd5748e43..8df52716c3a49 100644
--- a/backend/drive/drive_internal_test.go
+++ b/backend/drive/drive_internal_test.go
@@ -378,9 +378,9 @@ func (f *Fs) InternalTestUnTrash(t *testing.T) {
// Make some objects, one in a subdir
contents := random.String(100)
file1 := fstest.NewItem("trashDir/toBeTrashed", contents, time.Now())
- _, obj1 := fstests.PutTestContents(ctx, t, f, &file1, contents, false)
+ obj1 := fstests.PutTestContents(ctx, t, f, &file1, contents, false)
file2 := fstest.NewItem("trashDir/subdir/toBeTrashed", contents, time.Now())
- _, _ = fstests.PutTestContents(ctx, t, f, &file2, contents, false)
+ _ = fstests.PutTestContents(ctx, t, f, &file2, contents, false)
// Check objects
checkObjects := func() {
@@ -496,7 +496,7 @@ func (f *Fs) InternalTestAgeQuery(t *testing.T) {
require.NoError(t, err)
file1 := fstest.Item{ModTime: time.Now(), Path: "agequery.txt"}
- _, _ = fstests.PutTestContents(defCtx, t, tempFs1, &file1, "abcxyz", true)
+ _ = fstests.PutTestContents(defCtx, t, tempFs1, &file1, "abcxyz", true)
// validate sync/copy
const timeQuery = "(modifiedTime >= '"
diff --git a/backend/hasher/hasher_internal_test.go b/backend/hasher/hasher_internal_test.go
index 3ec7a11f94eae..5332318344882 100644
--- a/backend/hasher/hasher_internal_test.go
+++ b/backend/hasher/hasher_internal_test.go
@@ -19,7 +19,7 @@ import (
func putFile(ctx context.Context, t *testing.T, f fs.Fs, name, data string) fs.Object {
mtime1 := fstest.Time("2001-02-03T04:05:06.499999999Z")
item := fstest.Item{Path: name, ModTime: mtime1}
- _, o := fstests.PutTestContents(ctx, t, f, &item, data, true)
+ o := fstests.PutTestContents(ctx, t, f, &item, data, true)
require.NotNil(t, o)
return o
}
diff --git a/backend/union/union_internal_test.go b/backend/union/union_internal_test.go
index 22443dd053572..b6f78ec8d4900 100644
--- a/backend/union/union_internal_test.go
+++ b/backend/union/union_internal_test.go
@@ -38,7 +38,7 @@ func (f *Fs) TestInternalReadOnly(t *testing.T) {
// Put a file onto the read only fs
contents := random.String(50)
file1 := fstest.NewItem(dir+"/file.txt", contents, time.Now())
- _, obj1 := fstests.PutTestContents(ctx, t, rofs, &file1, contents, true)
+ obj1 := fstests.PutTestContents(ctx, t, rofs, &file1, contents, true)
// Check read from readonly fs via union
o, err := f.NewObject(ctx, file1.Path)
@@ -109,14 +109,14 @@ func TestMoveCopy(t *testing.T) {
// Put a file onto the local fs
contentsLocal := random.String(50)
fileLocal := fstest.NewItem("local.txt", contentsLocal, time.Now())
- _, _ = fstests.PutTestContents(ctx, t, fLocal, &fileLocal, contentsLocal, true)
+ _ = fstests.PutTestContents(ctx, t, fLocal, &fileLocal, contentsLocal, true)
objLocal, err := f.NewObject(ctx, fileLocal.Path)
require.NoError(t, err)
// Put a file onto the memory fs
contentsMemory := random.String(60)
fileMemory := fstest.NewItem("memory.txt", contentsMemory, time.Now())
- _, _ = fstests.PutTestContents(ctx, t, fMemory, &fileMemory, contentsMemory, true)
+ _ = fstests.PutTestContents(ctx, t, fMemory, &fileMemory, contentsMemory, true)
objMemory, err := f.NewObject(ctx, fileMemory.Path)
require.NoError(t, err)
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 02a44d385cf3e..4b65f689409cb 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -297,7 +297,7 @@ func TestHashSumsWithErrors(t *testing.T) {
// Make a test file
content := "-"
item1 := fstest.NewItem("file1", content, t1)
- _, _ = fstests.PutTestContents(ctx, t, memFs, &item1, content, true)
+ _ = fstests.PutTestContents(ctx, t, memFs, &item1, content, true)
// MemoryFS supports MD5
buf := &bytes.Buffer{}
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 58481416a56b9..d9de908c1dd1a 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -178,7 +178,7 @@ var _ fs.MimeTyper = (*objectInfoWithMimeType)(nil)
// putTestContentsMimeType puts file with given contents to the remote and checks it but unlike TestPutLarge doesn't remove
//
// it uploads the object with the mimeType passed in if set
-func putTestContentsMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, contents string, check bool, mimeType string) (string, fs.Object) {
+func putTestContentsMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, contents string, check bool, mimeType string) fs.Object {
var (
err error
obj fs.Object
@@ -204,22 +204,24 @@ func putTestContentsMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *f
obj = findObject(ctx, t, f, file.Path)
file.Check(t, obj, f.Precision())
}
- return contents, obj
+ return obj
}
// PutTestContents puts file with given contents to the remote and checks it but unlike TestPutLarge doesn't remove
-func PutTestContents(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, contents string, check bool) (string, fs.Object) {
+func PutTestContents(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, contents string, check bool) fs.Object {
return putTestContentsMimeType(ctx, t, f, file, contents, check, "")
}
// testPut puts file with random contents to the remote
func testPut(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item) (string, fs.Object) {
- return PutTestContents(ctx, t, f, file, random.String(100), true)
+ contents := random.String(100)
+ return contents, PutTestContents(ctx, t, f, file, contents, true)
}
// testPutMimeType puts file with random contents to the remote and the mime type given
func testPutMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, mimeType string) (string, fs.Object) {
- return putTestContentsMimeType(ctx, t, f, file, random.String(100), true, mimeType)
+ contents := random.String(100)
+ return contents, putTestContentsMimeType(ctx, t, f, file, contents, true, mimeType)
}
// TestPutLarge puts file to the remote, checks it and removes it on success.
From 6a0e021dacb5a3c4840cde35ec26e3f254121de5 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 24 May 2022 11:00:00 +0100
Subject: [PATCH 116/560] fs: implement optional Metadata interface for Objects
#111
This implements integration tests for the feature also.
---
docs/content/docs.md | 126 ++++++++++++++++++++++++++++++++++++
docs/content/overview.md | 100 ++++++++++++++++------------
fs/features.go | 6 ++
fs/metadata.go | 29 +++++++++
fs/metadata_test.go | 17 +++++
fs/operations/operations.go | 10 +++
fs/types.go | 13 ++++
fstest/fstests/fstests.go | 114 ++++++++++++++++++++++++++++----
8 files changed, 362 insertions(+), 53 deletions(-)
create mode 100644 fs/metadata.go
create mode 100644 fs/metadata_test.go
diff --git a/docs/content/docs.md b/docs/content/docs.md
index 66777e1b8981d..7cfffe2b44d94 100644
--- a/docs/content/docs.md
+++ b/docs/content/docs.md
@@ -425,6 +425,126 @@ This can be used when scripting to make aged backups efficiently, e.g.
rclone sync -i remote:current-backup remote:previous-backup
rclone sync -i /path/to/files remote:current-backup
+## Metadata support {#metadata}
+
+Metadata is data about a file which isn't the contents of the file.
+Normally rclone only preserves the modification time and the content
+(MIME) type where possible.
+
+Rclone supports preserving all the available metadata on files (not
+directories) when using the `--metadata` or `-M` flag.
+
+Exactly what metadata is supported and what that support means depends
+on the backend. Backends that support metadata have a metadata section
+in their docs and are listed in the [features table](/overview/#features)
+(Eg [local](/local/#metadata), [s3](/s3/#metadata))
+
+Rclone only supports a one-time sync of metadata. This means that
+metadata will be synced from the source object to the destination
+object only when the source object has changed and needs to be
+re-uploaded. If the metadata subsequently changes on the source object
+without changing the object itself then it won't be synced to the
+destination object. This is in line with the way rclone syncs
+`Content-Type` without the `--metadata` flag.
+
+Using `--metadata` when syncing from local to local will preserve file
+attributes such as file mode, owner, extended attributes (not
+Windows).
+
+Note that arbitrary metadata may be added to objects using the
+`--upload-metadata key=value` flag when the object is first uploaded.
+This flag can be repeated as many times as necessary.
+
+### Types of metadata
+
+Metadata is divided into two type. System metadata and User metadata.
+
+Metadata which the backend uses itself is called system metadata. For
+example on the local backend the system metadata `uid` will store the
+user ID of the file when used on a unix based platform.
+
+Arbitrary metadata is called user metadata and this can be set however
+is desired.
+
+When objects are copied from backend to backend, they will attempt to
+interpret system metadata if it is supplied. Metadata may change from
+being user metadata to system metadata as objects are copied between
+different backends. For example copying an object from s3 sets the
+`content-type` metadata. In a backend which understands this (like
+`azureblob`) this will become the Content-Type of the object. In a
+backend which doesn't understand this (like the `local` backend) this
+will become user metadata. However should the local object be copied
+back to s3, the Content-Type will be set correctly.
+
+### Metadata framework
+
+Rclone implements a metadata framework which can read metadata from an
+object and write it to the object when (and only when) it is being
+uploaded.
+
+This metadata is stored as a dictionary with string keys and string
+values.
+
+There are some limits on the names of the keys (these may be clarified
+further in the future).
+
+- must be lower case
+- may be `a-z` `0-9` containing `.` `-` or `_`
+- length is backend dependent
+
+Each backend can provide system metadata that it understands. Some
+backends can also store arbitrary user metadata.
+
+Where possible the key names are standardized, so, for example, it is
+possible to copy object metadata from s3 to azureblob for example and
+metadata will be translated apropriately.
+
+Some backends have limits on the size of the metadata and rclone will
+give errors on upload if they are exceeded.
+
+### Metadata preservation
+
+The goal of the implementation is to
+
+1. Preserve metadata if at all possible
+2. Interpret metadata if at all possible
+
+The consequences of 1 is that you can copy an S3 object to a local
+disk then back to S3 losslessly. Likewise you can copy a local file
+with file attributes and xattrs from local disk to s3 and back again
+losslessly.
+
+The consequence of 2 is that you can copy an S3 object with metadata
+to Azureblob (say) and have the metadata appear on the Azureblob
+object also.
+
+### Standard system metadata
+
+Here is a table of standard system metadata which, if appropriate, a
+backend may implement.
+
+| key | description | example |
+|---------------------|-------------|---------|
+| mode | File type and mode: octal, unix style | 0100664 |
+| uid | User ID of owner: decimal number | 500 |
+| gid | Group ID of owner: decimal number | 500 |
+| rdev | Device ID (if special file) => hexadecimal | 0 |
+| atime | Time of last access: RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 |
+| mtime | Time of last modification: RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 |
+| btime | Time of file creation (birth): RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 |
+| cache-control | Cache-Control header | no-cache |
+| content-disposition | Content-Disposition header | inline |
+| content-encoding | Content-Encoding header | gzip |
+| content-language | Content-Language header | en-US |
+| content-type | Content-Type header | text/plain |
+
+The metadata keys `mtime` and `content-type` will take precedence if
+supplied in the metadata over reading the `Content-Type` or
+modification time of the source object.
+
+Hashes are not included in system metadata as there is a well defined
+way of reading those already.
+
Options
-------
@@ -1206,6 +1326,12 @@ When the limit is reached all transfers will stop immediately.
Rclone will exit with exit code 8 if the transfer limit is reached.
+## --metadata / -M
+
+Setting this flag enables rclone to copy the metadata from the source
+to the destination. For local backends this is ownership, permissions,
+xattr etc. See the [#metadata](metadata section) for more info.
+
### --cutoff-mode=hard|soft|cautious ###
This modifies the behavior of `--max-transfer`
diff --git a/docs/content/overview.md b/docs/content/overview.md
index c107723eab65c..74194f28f3f61 100644
--- a/docs/content/overview.md
+++ b/docs/content/overview.md
@@ -14,48 +14,48 @@ show through.
Here is an overview of the major features of each cloud storage system.
-| Name | Hash | ModTime | Case Insensitive | Duplicate Files | MIME Type |
-| ---------------------------- |:----------------:|:-------:|:----------------:|:---------------:|:---------:|
-| 1Fichier | Whirlpool | - | No | Yes | R |
-| Akamai Netstorage | MD5, SHA256 | R/W | No | No | R |
-| Amazon Drive | MD5 | - | Yes | No | R |
-| Amazon S3 (or S3 compatible) | MD5 | R/W | No | No | R/W |
-| Backblaze B2 | SHA1 | R/W | No | No | R/W |
-| Box | SHA1 | R/W | Yes | No | - |
-| Citrix ShareFile | MD5 | R/W | Yes | No | - |
-| Dropbox | DBHASH ¹ | R | Yes | No | - |
-| Enterprise File Fabric | - | R/W | Yes | No | R/W |
-| FTP | - | R/W ¹⁰ | No | No | - |
-| Google Cloud Storage | MD5 | R/W | No | No | R/W |
-| Google Drive | MD5 | R/W | No | Yes | R/W |
-| Google Photos | - | - | No | Yes | R |
-| HDFS | - | R/W | No | No | - |
-| HTTP | - | R | No | No | R |
-| Hubic | MD5 | R/W | No | No | R/W |
-| Internet Archive | MD5, SHA1, CRC32 | R/W ¹¹ | No | No | - |
-| Jottacloud | MD5 | R/W | Yes | No | R |
-| Koofr | MD5 | - | Yes | No | - |
-| Mail.ru Cloud | Mailru ⁶ | R/W | Yes | No | - |
-| Mega | - | - | No | Yes | - |
-| Memory | MD5 | R/W | No | No | - |
-| Microsoft Azure Blob Storage | MD5 | R/W | No | No | R/W |
-| Microsoft OneDrive | SHA1 ⁵ | R/W | Yes | No | R |
-| OpenDrive | MD5 | R/W | Yes | Partial ⁸ | - |
-| OpenStack Swift | MD5 | R/W | No | No | R/W |
-| pCloud | MD5, SHA1 ⁷ | R | No | No | W |
-| premiumize.me | - | - | Yes | No | R |
-| put.io | CRC-32 | R/W | No | Yes | R |
-| QingStor | MD5 | - ⁹ | No | No | R/W |
-| Seafile | - | - | No | No | - |
-| SFTP | MD5, SHA1 ² | R/W | Depends | No | - |
-| Sia | - | - | No | No | - |
-| SugarSync | - | - | No | No | - |
-| Storj | - | R | No | No | - |
-| Uptobox | - | - | No | Yes | - |
-| WebDAV | MD5, SHA1 ³ | R ⁴ | Depends | No | - |
-| Yandex Disk | MD5 | R/W | No | No | R |
-| Zoho WorkDrive | - | - | No | No | - |
-| The local filesystem | All | R/W | Depends | No | - |
+| Name | Hash | ModTime | Case Insensitive | Duplicate Files | MIME Type | Metadata |
+| ---------------------------- |:----------------:|:-------:|:----------------:|:---------------:|:---------:|:--------:|
+| 1Fichier | Whirlpool | - | No | Yes | R | - |
+| Akamai Netstorage | MD5, SHA256 | R/W | No | No | R | - |
+| Amazon Drive | MD5 | - | Yes | No | R | - |
+| Amazon S3 (or S3 compatible) | MD5 | R/W | No | No | R/W | RWU |
+| Backblaze B2 | SHA1 | R/W | No | No | R/W | - |
+| Box | SHA1 | R/W | Yes | No | - | - |
+| Citrix ShareFile | MD5 | R/W | Yes | No | - | - |
+| Dropbox | DBHASH ¹ | R | Yes | No | - | - |
+| Enterprise File Fabric | - | R/W | Yes | No | R/W | - |
+| FTP | - | R/W ¹⁰ | No | No | - | - |
+| Google Cloud Storage | MD5 | R/W | No | No | R/W | - |
+| Google Drive | MD5 | R/W | No | Yes | R/W | - |
+| Google Photos | - | - | No | Yes | R | - |
+| HDFS | - | R/W | No | No | - | - |
+| HTTP | - | R | No | No | R | - |
+| Hubic | MD5 | R/W | No | No | R/W | - |
+| Internet Archive | MD5, SHA1, CRC32 | R/W ¹¹ | No | No | - | - |
+| Jottacloud | MD5 | R/W | Yes | No | R | - |
+| Koofr | MD5 | - | Yes | No | - | - |
+| Mail.ru Cloud | Mailru ⁶ | R/W | Yes | No | - | - |
+| Mega | - | - | No | Yes | - | - |
+| Memory | MD5 | R/W | No | No | - | - |
+| Microsoft Azure Blob Storage | MD5 | R/W | No | No | R/W | - |
+| Microsoft OneDrive | SHA1 ⁵ | R/W | Yes | No | R | - |
+| OpenDrive | MD5 | R/W | Yes | Partial ⁸ | - | - |
+| OpenStack Swift | MD5 | R/W | No | No | R/W | - |
+| pCloud | MD5, SHA1 ⁷ | R | No | No | W | - |
+| premiumize.me | - | - | Yes | No | R | - |
+| put.io | CRC-32 | R/W | No | Yes | R | - |
+| QingStor | MD5 | - ⁹ | No | No | R/W | - |
+| Seafile | - | - | No | No | - | - |
+| SFTP | MD5, SHA1 ² | R/W | Depends | No | - | - |
+| Sia | - | - | No | No | - | - |
+| SugarSync | - | - | No | No | - | - |
+| Storj | - | R | No | No | - | - |
+| Uptobox | - | - | No | Yes | - | - |
+| WebDAV | MD5, SHA1 ³ | R ⁴ | Depends | No | - | - |
+| Yandex Disk | MD5 | R/W | No | No | R | - |
+| Zoho WorkDrive | - | - | No | No | - | - |
+| The local filesystem | All | R/W | Depends | No | - | RWU |
### Notes
@@ -438,6 +438,22 @@ remote which supports writing (`W`) then rclone will preserve the MIME
types. Otherwise they will be guessed from the extension, or the
remote itself may assign the MIME type.
+### Metadata
+
+Backends may or may support reading or writing metadata. They may
+support reading and writing system metadata (metadata intrinsic to
+that backend) and/or user metadata (general purpose metadata).
+
+The levels of metadata support are
+
+| Key | Explanation |
+|-----|-------------|
+| `R` | Read only System Metadata |
+| `RW` | Read and write System Metadata |
+| `RWU` | Read and write System Metadata and read and write User Metadata |
+
+See [the metadata docs](/docs/#metadata) for more info.
+
## Optional Features ##
All rclone remotes support a base command set. Other features depend
diff --git a/fs/features.go b/fs/features.go
index 6db6bd548c07c..6fce02d608df8 100644
--- a/fs/features.go
+++ b/fs/features.go
@@ -26,6 +26,9 @@ type Features struct {
IsLocal bool // is the local backend
SlowModTime bool // if calling ModTime() generally takes an extra transaction
SlowHash bool // if calling Hash() generally takes an extra transaction
+ ReadMetadata bool // can read metadata from objects
+ WriteMetadata bool // can write metadata to objects
+ UserMetadata bool // can read/write general purpose metadata
// Purge all files in the directory specified
//
@@ -305,6 +308,9 @@ func (ft *Features) Mask(ctx context.Context, f Fs) *Features {
ft.DuplicateFiles = ft.DuplicateFiles && mask.DuplicateFiles
ft.ReadMimeType = ft.ReadMimeType && mask.ReadMimeType
ft.WriteMimeType = ft.WriteMimeType && mask.WriteMimeType
+ ft.ReadMetadata = ft.ReadMetadata && mask.ReadMetadata
+ ft.WriteMetadata = ft.WriteMetadata && mask.WriteMetadata
+ ft.UserMetadata = ft.UserMetadata && mask.UserMetadata
ft.CanHaveEmptyDirectories = ft.CanHaveEmptyDirectories && mask.CanHaveEmptyDirectories
ft.BucketBased = ft.BucketBased && mask.BucketBased
ft.BucketBasedRootOK = ft.BucketBasedRootOK && mask.BucketBasedRootOK
diff --git a/fs/metadata.go b/fs/metadata.go
new file mode 100644
index 0000000000000..dcdb517dc7d73
--- /dev/null
+++ b/fs/metadata.go
@@ -0,0 +1,29 @@
+package fs
+
+import "context"
+
+// Metadata represents Object metadata in a standardised form
+//
+// See docs/content/metadata.md for the interpretation of the keys
+type Metadata map[string]string
+
+// Set k to v on m
+//
+// If m is nil, then it will get made
+func (m *Metadata) Set(k, v string) {
+ if *m == nil {
+ *m = make(Metadata, 1)
+ }
+ (*m)[k] = v
+}
+
+// GetMetadata from an ObjectInfo
+//
+// If the object has no metadata then metadata will be nil
+func GetMetadata(ctx context.Context, o ObjectInfo) (metadata Metadata, err error) {
+ do, ok := o.(Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
diff --git a/fs/metadata_test.go b/fs/metadata_test.go
new file mode 100644
index 0000000000000..bc72c3964d310
--- /dev/null
+++ b/fs/metadata_test.go
@@ -0,0 +1,17 @@
+package fs
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMetadataSet(t *testing.T) {
+ var m Metadata
+ assert.Nil(t, m)
+ m.Set("key", "value")
+ assert.NotNil(t, m)
+ assert.Equal(t, "value", m["key"])
+ m.Set("key", "value2")
+ assert.Equal(t, "value2", m["key"])
+}
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index bdeeaf03686e3..a9c2b524c3582 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -352,6 +352,16 @@ func (o *OverrideRemote) GetTier() string {
return ""
}
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *OverrideRemote) Metadata(ctx context.Context) (fs.Metadata, error) {
+ if do, ok := o.ObjectInfo.(fs.Metadataer); ok {
+ return do.Metadata(ctx)
+ }
+ return nil, nil
+}
+
// Check all optional interfaces satisfied
var _ fs.FullObjectInfo = (*OverrideRemote)(nil)
diff --git a/fs/types.go b/fs/types.go
index b340ce67b98e7..88ff5e8cd390e 100644
--- a/fs/types.go
+++ b/fs/types.go
@@ -183,6 +183,14 @@ type GetTierer interface {
GetTier() string
}
+// Metadataer is an optional interface for Object
+type Metadataer interface {
+ // Metadata returns metadata for an object
+ //
+ // It should return nil if there is no Metadata
+ Metadata(ctx context.Context) (Metadata, error)
+}
+
// FullObjectInfo contains all the read-only optional interfaces
//
// Use for checking making wrapping ObjectInfos implement everything
@@ -192,6 +200,7 @@ type FullObjectInfo interface {
IDer
ObjectUnWrapper
GetTierer
+ Metadataer
}
// FullObject contains all the optional interfaces for Object
@@ -204,6 +213,7 @@ type FullObject interface {
ObjectUnWrapper
GetTierer
SetTierer
+ Metadataer
}
// ObjectOptionalInterfaces returns the names of supported and
@@ -232,6 +242,9 @@ func ObjectOptionalInterfaces(o Object) (supported, unsupported []string) {
_, ok = o.(GetTierer)
store(ok, "GetTier")
+ _, ok = o.(Metadataer)
+ store(ok, "Metadata")
+
return supported, unsupported
}
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index d9de908c1dd1a..47b841bcaf327 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -157,13 +157,15 @@ func retry(t *testing.T, what string, f func() error) {
type objectInfoWithMimeType struct {
fs.ObjectInfo
mimeType string
+ metadata fs.Metadata
}
// Return a wrapped fs.ObjectInfo which returns the mime type given
-func overrideMimeType(o fs.ObjectInfo, mimeType string) fs.ObjectInfo {
+func overrideMimeType(o fs.ObjectInfo, mimeType string, metadata fs.Metadata) fs.ObjectInfo {
return &objectInfoWithMimeType{
ObjectInfo: o,
mimeType: mimeType,
+ metadata: metadata,
}
}
@@ -172,13 +174,25 @@ func (o *objectInfoWithMimeType) MimeType(ctx context.Context) string {
return o.mimeType
}
+// Metadata that was overridden
+func (o *objectInfoWithMimeType) Metadata(ctx context.Context) (fs.Metadata, error) {
+ return o.metadata, nil
+}
+
+// check interfaces
+var (
+ _ fs.MimeTyper = (*objectInfoWithMimeType)(nil)
+ _ fs.Metadataer = (*objectInfoWithMimeType)(nil)
+)
+
// check interface
-var _ fs.MimeTyper = (*objectInfoWithMimeType)(nil)
-// putTestContentsMimeType puts file with given contents to the remote and checks it but unlike TestPutLarge doesn't remove
+// PutTestContentsMetadata puts file with given contents to the remote and checks it but unlike TestPutLarge doesn't remove
//
-// it uploads the object with the mimeType passed in if set
-func putTestContentsMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, contents string, check bool, mimeType string) fs.Object {
+// It uploads the object with the mimeType and metadata passed in if set
+//
+// It returns the object which will have been checked if check is set
+func PutTestContentsMetadata(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, contents string, check bool, mimeType string, metadata fs.Metadata) fs.Object {
var (
err error
obj fs.Object
@@ -191,14 +205,21 @@ func putTestContentsMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *f
file.Size = int64(buf.Len())
obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil)
- if mimeType != "" {
- obji = overrideMimeType(obji, mimeType)
+ if mimeType != "" || metadata != nil {
+ obji = overrideMimeType(obji, mimeType, metadata)
}
obj, err = f.Put(ctx, in, obji)
return err
})
file.Hashes = uploadHash.Sums()
if check {
+ // Overwrite time with that in metadata if it is already specified
+ mtime, ok := metadata["mtime"]
+ if ok {
+ modTime, err := time.Parse(time.RFC3339Nano, mtime)
+ require.NoError(t, err)
+ file.ModTime = modTime
+ }
file.Check(t, obj, f.Precision())
// Re-read the object and check again
obj = findObject(ctx, t, f, file.Path)
@@ -209,7 +230,7 @@ func putTestContentsMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *f
// PutTestContents puts file with given contents to the remote and checks it but unlike TestPutLarge doesn't remove
func PutTestContents(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, contents string, check bool) fs.Object {
- return putTestContentsMimeType(ctx, t, f, file, contents, check, "")
+ return PutTestContentsMetadata(ctx, t, f, file, contents, check, "", nil)
}
// testPut puts file with random contents to the remote
@@ -219,9 +240,9 @@ func testPut(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item) (str
}
// testPutMimeType puts file with random contents to the remote and the mime type given
-func testPutMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, mimeType string) (string, fs.Object) {
+func testPutMimeType(ctx context.Context, t *testing.T, f fs.Fs, file *fstest.Item, mimeType string, metadata fs.Metadata) (string, fs.Object) {
contents := random.String(100)
- return contents, putTestContentsMimeType(ctx, t, f, file, contents, true, mimeType)
+ return contents, PutTestContentsMetadata(ctx, t, f, file, contents, true, mimeType, metadata)
}
// TestPutLarge puts file to the remote, checks it and removes it on success.
@@ -350,6 +371,7 @@ func Run(t *testing.T, opt *Opt) {
}
file1Contents string
file1MimeType = "text/csv"
+ file1Metadata = fs.Metadata{"rclone-test": "potato"}
file2 = fstest.Item{
ModTime: fstest.Time("2001-02-03T04:05:10.123123123Z"),
Path: `hello? sausage/êé/Hello, 世界/ " ' @ < > & ? + ≠/z.txt`,
@@ -868,7 +890,7 @@ func Run(t *testing.T, opt *Opt) {
skipIfNotOk(t)
file1Contents, _ = testPut(ctx, t, f, &file1)
/* file2Contents = */ testPut(ctx, t, f, &file2)
- file1Contents, _ = testPutMimeType(ctx, t, f, &file1, file1MimeType)
+ file1Contents, _ = testPutMimeType(ctx, t, f, &file1, file1MimeType, file1Metadata)
// Note that the next test will check there are no duplicated file names
// TestFsListDirFile2 tests the files are correctly uploaded by doing
@@ -1357,6 +1379,76 @@ func Run(t *testing.T, opt *Opt) {
}
})
+ // TestObjectMetadata tests the Metadata of the object is correct
+ t.Run("ObjectMetadata", func(t *testing.T) {
+ skipIfNotOk(t)
+ features := f.Features()
+ obj := findObject(ctx, t, f, file1.Path)
+ do, ok := obj.(fs.Metadataer)
+ if !ok {
+ require.False(t, features.ReadMetadata, "Features.ReadMetadata is set but Object.Metadata method not found")
+ t.Skip("Metadata method not supported")
+ }
+ metadata, err := do.Metadata(ctx)
+ require.NoError(t, err)
+ // check standard metadata
+ for k, v := range metadata {
+ switch k {
+ case "atime", "btime", "mtime":
+ mtime, err := time.Parse(time.RFC3339Nano, v)
+ require.NoError(t, err)
+ if k == "mtime" {
+ fstest.AssertTimeEqualWithPrecision(t, file1.Path, file1.ModTime, mtime, f.Precision())
+ }
+ }
+ }
+ if !features.ReadMetadata {
+ if metadata != nil {
+ require.Equal(t, "", metadata, "Features.ReadMetadata is not set but Object.Metadata returned a non nil Metadata")
+ }
+ } else if features.WriteMetadata {
+ require.NotNil(t, metadata)
+ if features.UserMetadata {
+ // check all the metadata bits we uploaded are present - there may be more we didn't write
+ for k, v := range file1Metadata {
+ assert.Equal(t, v, metadata[k], "can read and write metadata but failed on key %q", k)
+ }
+ }
+ // Now test we can set the mtime and content-type via the metadata and these take precedence
+ t.Run("mtime", func(t *testing.T) {
+ path := "metadatatest"
+ mtimeModTime := fstest.Time("2002-02-03T04:05:06.499999999Z")
+ modTime := fstest.Time("2003-02-03T04:05:06.499999999Z")
+ item := fstest.NewItem(path, path, modTime)
+ metaMimeType := "application/zip"
+ mimeType := "application/gzip"
+ metadata := fs.Metadata{
+ "mtime": mtimeModTime.Format(time.RFC3339Nano),
+ "content-type": metaMimeType,
+ }
+ // This checks the mtime is correct also and returns the re-read object
+ _, obj := testPutMimeType(ctx, t, f, &item, mimeType, metadata)
+ defer func() {
+ assert.NoError(t, obj.Remove(ctx))
+ }()
+ // Check content-type got updated too
+ if features.ReadMimeType && features.WriteMimeType {
+ // read the object from scratch
+ o, err := f.NewObject(ctx, path)
+ require.NoError(t, err)
+
+ // Check the mimetype is correct
+ do, ok := o.(fs.MimeTyper)
+ require.True(t, ok)
+ gotMimeType := do.MimeType(ctx)
+ assert.Equal(t, metaMimeType, gotMimeType)
+ }
+ })
+ } else {
+ // Have some metadata here we didn't write - can't really check it!
+ }
+ })
+
// TestObjectSetModTime tests that SetModTime works
t.Run("ObjectSetModTime", func(t *testing.T) {
skipIfNotOk(t)
From 0652ec95db726f8895cb4aa402df04417439c270 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 22 Jun 2022 15:56:41 +0100
Subject: [PATCH 117/560] fs: implement MetadataInfo to show info about
metadata in help and rc
Info about this will appear in operations/fsinfo and in the backend
help (`rclone help backend s3`).
---
cmd/help.go | 25 +++++++
fs/metadata.go | 14 ++++
fs/operations/operations.go | 21 ++++--
fs/operations/rc.go | 137 +++++++++++++++++++++++++-----------
fs/registry.go | 2 +
fstest/fstests/fstests.go | 9 ++-
6 files changed, 160 insertions(+), 48 deletions(-)
diff --git a/cmd/help.go b/cmd/help.go
index db7e95da40e7f..b5f1c222707dd 100644
--- a/cmd/help.go
+++ b/cmd/help.go
@@ -6,6 +6,7 @@ import (
"log"
"os"
"regexp"
+ "sort"
"strings"
"github.com/rclone/rclone/fs"
@@ -362,4 +363,28 @@ func showBackend(name string) {
fmt.Printf("\n")
}
}
+ if backend.MetadataInfo != nil {
+ fmt.Printf("### Metadata\n\n")
+ fmt.Printf("%s\n\n", strings.TrimSpace(backend.MetadataInfo.Help))
+ if len(backend.MetadataInfo.System) > 0 {
+ fmt.Printf("Here are the possible system metadata items for the %s backend.\n\n", backend.Name)
+ keys := []string{}
+ for k := range backend.MetadataInfo.System {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ fmt.Printf("| Name | Help | Type | Example | Read Only |\n")
+ fmt.Printf("|------|------|------|---------|-----------|\n")
+ for _, k := range keys {
+ v := backend.MetadataInfo.System[k]
+ ro := "N"
+ if v.ReadOnly {
+ ro = "**Y**"
+ }
+ fmt.Printf("| %s | %s | %s | %s | %s |\n", k, v.Help, v.Type, v.Example, ro)
+ }
+ fmt.Printf("\n")
+ }
+ fmt.Printf("See the [metadata](/docs/#metadata) docs for more info.\n\n")
+ }
}
diff --git a/fs/metadata.go b/fs/metadata.go
index dcdb517dc7d73..3df5a29f5d33b 100644
--- a/fs/metadata.go
+++ b/fs/metadata.go
@@ -7,6 +7,20 @@ import "context"
// See docs/content/metadata.md for the interpretation of the keys
type Metadata map[string]string
+// MetadataHelp represents help for a bit of system metadata
+type MetadataHelp struct {
+ Help string
+ Type string
+ Example string
+ ReadOnly bool
+}
+
+// MetadataInfo is help for the whole metadata for this backend.
+type MetadataInfo struct {
+ System map[string]MetadataHelp
+ Help string
+}
+
// Set k to v on m
//
// If m is nil, then it will get made
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index a9c2b524c3582..aa69a0cbf692c 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -2278,21 +2278,30 @@ type FsInfo struct {
// Features returns the optional features of this Fs
Features map[string]bool
+
+ // MetadataInfo returns info about the metadata for this backend
+ MetadataInfo *fs.MetadataInfo
}
// GetFsInfo gets the information (FsInfo) about a given Fs
func GetFsInfo(f fs.Fs) *FsInfo {
+ features := f.Features()
info := &FsInfo{
- Name: f.Name(),
- Root: f.Root(),
- String: f.String(),
- Precision: f.Precision(),
- Hashes: make([]string, 0, 4),
- Features: f.Features().Enabled(),
+ Name: f.Name(),
+ Root: f.Root(),
+ String: f.String(),
+ Precision: f.Precision(),
+ Hashes: make([]string, 0, 4),
+ Features: features.Enabled(),
+ MetadataInfo: nil,
}
for _, hashType := range f.Hashes().Array() {
info.Hashes = append(info.Hashes, hashType.String())
}
+ fsInfo, _, _, _, err := fs.ParseRemote(fs.ConfigString(f))
+ if err == nil && fsInfo != nil && fsInfo.MetadataInfo != nil {
+ info.MetadataInfo = fsInfo.MetadataInfo
+ }
return info
}
diff --git a/fs/operations/rc.go b/fs/operations/rc.go
index b8c59ce2d8d87..bfa7e2599133d 100644
--- a/fs/operations/rc.go
+++ b/fs/operations/rc.go
@@ -413,46 +413,103 @@ This returns info about the remote passed in;
` + "```" + `
{
- // optional features and whether they are available or not
- "Features": {
- "About": true,
- "BucketBased": false,
- "CanHaveEmptyDirectories": true,
- "CaseInsensitive": false,
- "ChangeNotify": false,
- "CleanUp": false,
- "Copy": false,
- "DirCacheFlush": false,
- "DirMove": true,
- "DuplicateFiles": false,
- "GetTier": false,
- "ListR": false,
- "MergeDirs": false,
- "Move": true,
- "OpenWriterAt": true,
- "PublicLink": false,
- "Purge": true,
- "PutStream": true,
- "PutUnchecked": false,
- "ReadMimeType": false,
- "ServerSideAcrossConfigs": false,
- "SetTier": false,
- "SetWrapper": false,
- "UnWrap": false,
- "WrapFs": false,
- "WriteMimeType": false
- },
- // Names of hashes available
- "Hashes": [
- "MD5",
- "SHA-1",
- "DropboxHash",
- "QuickXorHash"
- ],
- "Name": "local", // Name as created
- "Precision": 1, // Precision of timestamps in ns
- "Root": "/", // Path as created
- "String": "Local file system at /" // how the remote will appear in logs
+ // optional features and whether they are available or not
+ "Features": {
+ "About": true,
+ "BucketBased": false,
+ "BucketBasedRootOK": false,
+ "CanHaveEmptyDirectories": true,
+ "CaseInsensitive": false,
+ "ChangeNotify": false,
+ "CleanUp": false,
+ "Command": true,
+ "Copy": false,
+ "DirCacheFlush": false,
+ "DirMove": true,
+ "Disconnect": false,
+ "DuplicateFiles": false,
+ "GetTier": false,
+ "IsLocal": true,
+ "ListR": false,
+ "MergeDirs": false,
+ "MetadataInfo": true,
+ "Move": true,
+ "OpenWriterAt": true,
+ "PublicLink": false,
+ "Purge": true,
+ "PutStream": true,
+ "PutUnchecked": false,
+ "ReadMetadata": true,
+ "ReadMimeType": false,
+ "ServerSideAcrossConfigs": false,
+ "SetTier": false,
+ "SetWrapper": false,
+ "Shutdown": false,
+ "SlowHash": true,
+ "SlowModTime": false,
+ "UnWrap": false,
+ "UserInfo": false,
+ "UserMetadata": true,
+ "WrapFs": false,
+ "WriteMetadata": true,
+ "WriteMimeType": false
+ },
+ // Names of hashes available
+ "Hashes": [
+ "md5",
+ "sha1",
+ "whirlpool",
+ "crc32",
+ "sha256",
+ "dropbox",
+ "mailru",
+ "quickxor"
+ ],
+ "Name": "local", // Name as created
+ "Precision": 1, // Precision of timestamps in ns
+ "Root": "/", // Path as created
+ "String": "Local file system at /", // how the remote will appear in logs
+ // Information about the system metadata for this backend
+ "MetadataInfo": {
+ "System": {
+ "atime": {
+ "Help": "Time of last access",
+ "Type": "RFC 3339",
+ "Example": "2006-01-02T15:04:05.999999999Z07:00"
+ },
+ "btime": {
+ "Help": "Time of file birth (creation)",
+ "Type": "RFC 3339",
+ "Example": "2006-01-02T15:04:05.999999999Z07:00"
+ },
+ "gid": {
+ "Help": "Group ID of owner",
+ "Type": "decimal number",
+ "Example": "500"
+ },
+ "mode": {
+ "Help": "File type and mode",
+ "Type": "octal, unix style",
+ "Example": "0100664"
+ },
+ "mtime": {
+ "Help": "Time of last modification",
+ "Type": "RFC 3339",
+ "Example": "2006-01-02T15:04:05.999999999Z07:00"
+ },
+ "rdev": {
+ "Help": "Device ID (if special file)",
+ "Type": "hexadecimal",
+ "Example": "1abc"
+ },
+ "uid": {
+ "Help": "User ID of owner",
+ "Type": "decimal number",
+ "Example": "500"
+ }
+ },
+ "Help": "Textual help string\n"
+ }
}
` + "```" + `
diff --git a/fs/registry.go b/fs/registry.go
index 2f4afc7fe7c99..d380c7e0cb658 100644
--- a/fs/registry.go
+++ b/fs/registry.go
@@ -40,6 +40,8 @@ type RegInfo struct {
Aliases []string
// Hide - if set don't show in the configurator
Hide bool
+ // MetadataInfo help about the metadata in use in this backend
+ MetadataInfo *MetadataInfo
}
// FileName returns the on disk file name for this backend
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 47b841bcaf327..5d24296a04c61 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -1384,8 +1384,13 @@ func Run(t *testing.T, opt *Opt) {
skipIfNotOk(t)
features := f.Features()
obj := findObject(ctx, t, f, file1.Path)
- do, ok := obj.(fs.Metadataer)
- if !ok {
+ do, objectHasMetadata := obj.(fs.Metadataer)
+ if objectHasMetadata || features.ReadMetadata || features.WriteMetadata || features.UserMetadata {
+ fsInfo, _, _, _, err := fs.ParseRemote(fs.ConfigString(f))
+ require.NoError(t, err)
+ require.NotNil(t, fsInfo.MetadataInfo, "Object declares metadata support but no MetadataInfo in RegInfo")
+ }
+ if !objectHasMetadata {
require.False(t, features.ReadMetadata, "Features.ReadMetadata is set but Object.Metadata method not found")
t.Skip("Metadata method not supported")
}
From c4451bc43a32a5117c0cbe5a96140c65ec6cf507 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 24 May 2022 15:46:07 +0100
Subject: [PATCH 118/560] fs: add --metadata-set flag to specify metadata for
uploads
---
docs/content/docs.md | 8 ++-
fs/config.go | 1 +
fs/config/configflags/configflags.go | 16 ++++++
fs/metadata.go | 36 +++++++++++++
fs/metadata_test.go | 79 ++++++++++++++++++++++++++++
fs/open_options.go | 18 +++++++
fs/open_options_test.go | 10 ++++
fs/operations/operations.go | 6 +++
8 files changed, 173 insertions(+), 1 deletion(-)
diff --git a/docs/content/docs.md b/docs/content/docs.md
index 7cfffe2b44d94..a2bc38b078249 100644
--- a/docs/content/docs.md
+++ b/docs/content/docs.md
@@ -452,7 +452,7 @@ attributes such as file mode, owner, extended attributes (not
Windows).
Note that arbitrary metadata may be added to objects using the
-`--upload-metadata key=value` flag when the object is first uploaded.
+`--metadata-set key=value` flag when the object is first uploaded.
This flag can be repeated as many times as necessary.
### Types of metadata
@@ -1332,6 +1332,12 @@ Setting this flag enables rclone to copy the metadata from the source
to the destination. For local backends this is ownership, permissions,
xattr etc. See the [#metadata](metadata section) for more info.
+### --metadata-set key=value
+
+Add metadata `key` = `value` when uploading. This can be repeated as
+many times as required. See the [#metadata](metadata section) for more
+info.
+
### --cutoff-mode=hard|soft|cautious ###
This modifies the behavior of `--max-transfer`
diff --git a/fs/config.go b/fs/config.go
index e96be37d0ec9a..dceb063bb91d4 100644
--- a/fs/config.go
+++ b/fs/config.go
@@ -124,6 +124,7 @@ type ConfigInfo struct {
UploadHeaders []*HTTPOption
DownloadHeaders []*HTTPOption
Headers []*HTTPOption
+ MetadataSet Metadata // extra metadata to write when uploading
RefreshTimes bool
NoConsole bool
TrafficClass uint8
diff --git a/fs/config/configflags/configflags.go b/fs/config/configflags/configflags.go
index 8e3e8d5584e6f..de9d08073097a 100644
--- a/fs/config/configflags/configflags.go
+++ b/fs/config/configflags/configflags.go
@@ -37,6 +37,7 @@ var (
uploadHeaders []string
downloadHeaders []string
headers []string
+ metadataSet []string
)
// AddFlags adds the non filing system specific flags to the command
@@ -129,6 +130,7 @@ func AddFlags(ci *fs.ConfigInfo, flagSet *pflag.FlagSet) {
flags.StringArrayVarP(flagSet, &uploadHeaders, "header-upload", "", nil, "Set HTTP header for upload transactions")
flags.StringArrayVarP(flagSet, &downloadHeaders, "header-download", "", nil, "Set HTTP header for download transactions")
flags.StringArrayVarP(flagSet, &headers, "header", "", nil, "Set HTTP header for all transactions")
+ flags.StringArrayVarP(flagSet, &metadataSet, "metadata-set", "", nil, "Add metadata key=value when uploading")
flags.BoolVarP(flagSet, &ci.RefreshTimes, "refresh-times", "", ci.RefreshTimes, "Refresh the modtime of remote files")
flags.BoolVarP(flagSet, &ci.NoConsole, "no-console", "", ci.NoConsole, "Hide console window (supported on Windows only)")
flags.StringVarP(flagSet, &dscp, "dscp", "", "", "Set DSCP value to connections, value or name, e.g. CS1, LE, DF, AF21")
@@ -271,6 +273,20 @@ func SetFlags(ci *fs.ConfigInfo) {
if len(headers) != 0 {
ci.Headers = ParseHeaders(headers)
}
+ if len(headers) != 0 {
+ ci.Headers = ParseHeaders(headers)
+ }
+ if len(metadataSet) != 0 {
+ ci.MetadataSet = make(fs.Metadata, len(metadataSet))
+ for _, kv := range metadataSet {
+ equal := strings.IndexRune(kv, '=')
+ if equal < 0 {
+ log.Fatalf("Failed to parse '%s' as metadata key=value.", kv)
+ }
+ ci.MetadataSet[strings.ToLower(kv[:equal])] = kv[equal+1:]
+ }
+ fs.Debugf(nil, "MetadataUpload %v", ci.MetadataSet)
+ }
if len(dscp) != 0 {
if value, ok := parseDSCP(dscp); ok {
ci.TrafficClass = value << 2
diff --git a/fs/metadata.go b/fs/metadata.go
index 3df5a29f5d33b..50d0340ed05fd 100644
--- a/fs/metadata.go
+++ b/fs/metadata.go
@@ -31,6 +31,30 @@ func (m *Metadata) Set(k, v string) {
(*m)[k] = v
}
+// Merge other into m
+//
+// If m is nil, then it will get made
+func (m *Metadata) Merge(other Metadata) {
+ for k, v := range other {
+ if *m == nil {
+ *m = make(Metadata, len(other))
+ }
+ (*m)[k] = v
+ }
+}
+
+// MergeOptions gets any Metadata from the options passed in and
+// stores it in m (which may be nil).
+//
+// If there is no m then metadata will be nil
+func (m *Metadata) MergeOptions(options []OpenOption) {
+ for _, opt := range options {
+ if metadataOption, ok := opt.(MetadataOption); ok {
+ m.Merge(Metadata(metadataOption))
+ }
+ }
+}
+
// GetMetadata from an ObjectInfo
//
// If the object has no metadata then metadata will be nil
@@ -41,3 +65,15 @@ func GetMetadata(ctx context.Context, o ObjectInfo) (metadata Metadata, err erro
}
return do.Metadata(ctx)
}
+
+// GetMetadataOptions from an ObjectInfo and merge it with any in options
+//
+// If the object has no metadata then metadata will be nil
+func GetMetadataOptions(ctx context.Context, o ObjectInfo, options []OpenOption) (metadata Metadata, err error) {
+ metadata, err = GetMetadata(ctx, o)
+ if err != nil {
+ return nil, err
+ }
+ metadata.MergeOptions(options)
+ return metadata, nil
+}
diff --git a/fs/metadata_test.go b/fs/metadata_test.go
index bc72c3964d310..ba982cbc1d0ee 100644
--- a/fs/metadata_test.go
+++ b/fs/metadata_test.go
@@ -1,6 +1,7 @@
package fs
import (
+ "fmt"
"testing"
"github.com/stretchr/testify/assert"
@@ -15,3 +16,81 @@ func TestMetadataSet(t *testing.T) {
m.Set("key", "value2")
assert.Equal(t, "value2", m["key"])
}
+
+func TestMetadataMerge(t *testing.T) {
+ for _, test := range []struct {
+ in Metadata
+ merge Metadata
+ want Metadata
+ }{
+ {
+ in: Metadata{},
+ merge: Metadata{},
+ want: Metadata{},
+ }, {
+ in: nil,
+ merge: nil,
+ want: nil,
+ }, {
+ in: nil,
+ merge: Metadata{},
+ want: nil,
+ }, {
+ in: nil,
+ merge: Metadata{"a": "1", "b": "2"},
+ want: Metadata{"a": "1", "b": "2"},
+ }, {
+ in: Metadata{"a": "1", "b": "2"},
+ merge: nil,
+ want: Metadata{"a": "1", "b": "2"},
+ }, {
+ in: Metadata{"a": "1", "b": "2"},
+ merge: Metadata{"b": "B", "c": "3"},
+ want: Metadata{"a": "1", "b": "B", "c": "3"},
+ },
+ } {
+ what := fmt.Sprintf("in=%v, merge=%v", test.in, test.merge)
+ test.in.Merge(test.merge)
+ assert.Equal(t, test.want, test.in, what)
+ }
+}
+
+func TestMetadataMergeOptions(t *testing.T) {
+ for _, test := range []struct {
+ in Metadata
+ opts []OpenOption
+ want Metadata
+ }{
+ {
+ opts: []OpenOption{},
+ want: nil,
+ }, {
+ opts: []OpenOption{&HTTPOption{}},
+ want: nil,
+ }, {
+ opts: []OpenOption{MetadataOption{"a": "1", "b": "2"}},
+ want: Metadata{"a": "1", "b": "2"},
+ }, {
+ opts: []OpenOption{
+ &HTTPOption{},
+ MetadataOption{"a": "1", "b": "2"},
+ MetadataOption{"b": "B", "c": "3"},
+ &HTTPOption{},
+ },
+ want: Metadata{"a": "1", "b": "B", "c": "3"},
+ }, {
+ in: Metadata{"a": "first", "z": "OK"},
+ opts: []OpenOption{
+ &HTTPOption{},
+ MetadataOption{"a": "1", "b": "2"},
+ MetadataOption{"b": "B", "c": "3"},
+ &HTTPOption{},
+ },
+ want: Metadata{"a": "1", "b": "B", "c": "3", "z": "OK"},
+ },
+ } {
+ what := fmt.Sprintf("in=%v, opts=%v", test.in, test.opts)
+ test.in.MergeOptions(test.opts)
+ assert.Equal(t, test.want, test.in, what)
+ }
+}
diff --git a/fs/open_options.go b/fs/open_options.go
index e357a9a6990d0..57daa132ab2c5 100644
--- a/fs/open_options.go
+++ b/fs/open_options.go
@@ -258,6 +258,24 @@ func (o NullOption) Mandatory() bool {
return false
}
+// MetadataOption defines an Option which does nothing
+type MetadataOption Metadata
+
+// Header formats the option as an http header
+func (o MetadataOption) Header() (key string, value string) {
+ return "", ""
+}
+
+// String formats the option into human-readable form
+func (o MetadataOption) String() string {
+ return fmt.Sprintf("MetadataOption(%v)", Metadata(o))
+}
+
+// Mandatory returns whether the option must be parsed or can be ignored
+func (o MetadataOption) Mandatory() bool {
+ return false
+}
+
// OpenOptionAddHeaders adds each header found in options to the
// headers map provided the key was non empty.
func OpenOptionAddHeaders(options []OpenOption, headers map[string]string) {
diff --git a/fs/open_options_test.go b/fs/open_options_test.go
index 829f45c1974c4..37463adeff7eb 100644
--- a/fs/open_options_test.go
+++ b/fs/open_options_test.go
@@ -132,6 +132,16 @@ func TestNullOption(t *testing.T) {
assert.Equal(t, false, opt.Mandatory())
}
+func TestMetadataOption(t *testing.T) {
+ opt := MetadataOption{"onion": "ice cream"}
+ var _ OpenOption = opt // check interface
+ assert.Equal(t, "MetadataOption(map[onion:ice cream])", opt.String())
+ key, value := opt.Header()
+ assert.Equal(t, "", key)
+ assert.Equal(t, "", value)
+ assert.Equal(t, false, opt.Mandatory())
+}
+
func TestFixRangeOptions(t *testing.T) {
for _, test := range []struct {
name string
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index aa69a0cbf692c..a41a0a009a160 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -489,6 +489,9 @@ func Copy(ctx context.Context, f fs.Fs, dst fs.Object, remote string, src fs.Obj
for _, option := range ci.UploadHeaders {
options = append(options, option)
}
+ if ci.MetadataSet != nil {
+ options = append(options, fs.MetadataOption(ci.MetadataSet))
+ }
if doUpdate {
actionTaken = "Copied (replaced existing)"
err = dst.Update(ctx, in, wrappedSrc, options...)
@@ -1381,6 +1384,9 @@ func Rcat(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser,
for _, option := range ci.UploadHeaders {
options = append(options, option)
}
+ if ci.MetadataSet != nil {
+ options = append(options, fs.MetadataOption(ci.MetadataSet))
+ }
compare := func(dst fs.Object) error {
var sums map[hash.Type]string
From 78d52882ca9fe3d35a6afe34d212281efb89a91e Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 25 May 2022 12:18:49 +0100
Subject: [PATCH 119/560] fs: add --metadata/-M flag to control whether
metadata is copied
---
fs/config.go | 1 +
fs/config/configflags/configflags.go | 1 +
fs/metadata.go | 6 ++++++
fstest/fstests/fstests.go | 9 +++++++++
4 files changed, 17 insertions(+)
diff --git a/fs/config.go b/fs/config.go
index dceb063bb91d4..a39779bae7b4d 100644
--- a/fs/config.go
+++ b/fs/config.go
@@ -134,6 +134,7 @@ type ConfigInfo struct {
HumanReadable bool
KvLockTime time.Duration // maximum time to keep key-value database locked by process
DisableHTTPKeepAlives bool
+ Metadata bool
}
// NewConfig creates a new config with everything set to the default
diff --git a/fs/config/configflags/configflags.go b/fs/config/configflags/configflags.go
index de9d08073097a..61aa65f91fe97 100644
--- a/fs/config/configflags/configflags.go
+++ b/fs/config/configflags/configflags.go
@@ -140,6 +140,7 @@ func AddFlags(ci *fs.ConfigInfo, flagSet *pflag.FlagSet) {
flags.BoolVarP(flagSet, &ci.HumanReadable, "human-readable", "", ci.HumanReadable, "Print numbers in a human-readable format, sizes with suffix Ki|Mi|Gi|Ti|Pi")
flags.DurationVarP(flagSet, &ci.KvLockTime, "kv-lock-time", "", ci.KvLockTime, "Maximum time to keep key-value database locked by process")
flags.BoolVarP(flagSet, &ci.DisableHTTPKeepAlives, "disable-http-keep-alives", "", ci.DisableHTTPKeepAlives, "Disable HTTP keep-alives and use each connection once.")
+ flags.BoolVarP(flagSet, &ci.Metadata, "metadata", "M", ci.Metadata, "If set, preserve metadata when copying objects")
}
// ParseHeaders converts the strings passed in via the header flags into HTTPOptions
diff --git a/fs/metadata.go b/fs/metadata.go
index 50d0340ed05fd..1fa48dc77e76a 100644
--- a/fs/metadata.go
+++ b/fs/metadata.go
@@ -68,8 +68,14 @@ func GetMetadata(ctx context.Context, o ObjectInfo) (metadata Metadata, err erro
// GetMetadataOptions from an ObjectInfo and merge it with any in options
//
+// If --metadata isn't in use it will return nil
+//
// If the object has no metadata then metadata will be nil
func GetMetadataOptions(ctx context.Context, o ObjectInfo, options []OpenOption) (metadata Metadata, err error) {
+ ci := GetConfig(ctx)
+ if !ci.Metadata {
+ return nil, nil
+ }
metadata, err = GetMetadata(ctx, o)
if err != nil {
return nil, err
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 5d24296a04c61..9cc95896eac41 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -206,6 +206,15 @@ func PutTestContentsMetadata(ctx context.Context, t *testing.T, f fs.Fs, file *f
file.Size = int64(buf.Len())
obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil)
if mimeType != "" || metadata != nil {
+ // force the --metadata flag on temporarily
+ if metadata != nil {
+ ci := fs.GetConfig(ctx)
+ previousMetadata := ci.Metadata
+ ci.Metadata = true
+ defer func() {
+ ci.Metadata = previousMetadata
+ }()
+ }
obji = overrideMimeType(obji, mimeType, metadata)
}
obj, err = f.Put(ctx, in, obji)
From d823a38ce57b22fc622946b5e6aaf07cb998c8be Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 24 May 2022 11:16:29 +0100
Subject: [PATCH 120/560] lsjson: add --metadata/-M flag
Note that this removes the `-M` flag from `--encrypted` as it
conflicted with the global flag and adds it to `--metadata`.
---
cmd/lsjson/lsjson.go | 6 +++++-
fs/operations/lsjson.go | 10 ++++++++++
fs/operations/lsjson_test.go | 13 +++++++++++++
fs/operations/rc.go | 1 +
4 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/cmd/lsjson/lsjson.go b/cmd/lsjson/lsjson.go
index 70b96a0f76513..8e4e343da0f68 100644
--- a/cmd/lsjson/lsjson.go
+++ b/cmd/lsjson/lsjson.go
@@ -26,10 +26,11 @@ func init() {
flags.BoolVarP(cmdFlags, &opt.ShowHash, "hash", "", false, "Include hashes in the output (may take longer)")
flags.BoolVarP(cmdFlags, &opt.NoModTime, "no-modtime", "", false, "Don't read the modification time (can speed things up)")
flags.BoolVarP(cmdFlags, &opt.NoMimeType, "no-mimetype", "", false, "Don't read the mime type (can speed things up)")
- flags.BoolVarP(cmdFlags, &opt.ShowEncrypted, "encrypted", "M", false, "Show the encrypted names")
+ flags.BoolVarP(cmdFlags, &opt.ShowEncrypted, "encrypted", "", false, "Show the encrypted names")
flags.BoolVarP(cmdFlags, &opt.ShowOrigIDs, "original", "", false, "Show the ID of the underlying Object")
flags.BoolVarP(cmdFlags, &opt.FilesOnly, "files-only", "", false, "Show only files in the listing")
flags.BoolVarP(cmdFlags, &opt.DirsOnly, "dirs-only", "", false, "Show only directories in the listing")
+ flags.BoolVarP(cmdFlags, &opt.Metadata, "metadata", "M", false, "Add metadata to the listing")
flags.StringArrayVarP(cmdFlags, &opt.HashTypes, "hash-type", "", nil, "Show only this hash type (may be repeated)")
flags.BoolVarP(cmdFlags, &statOnly, "stat", "", false, "Just return the info for the pointed to file")
}
@@ -81,6 +82,9 @@ returned
If ` + "`--files-only`" + ` is not specified directories in addition to the files
will be returned.
+If ` + "`--metadata`" + ` is set then an additional Metadata key will be returned.
+This will have metdata in rclone standard format as a JSON object.
+
if ` + "`--stat`" + ` is set then a single JSON blob will be returned about the
item pointed to. This will return an error if the item isn't found.
However on bucket based backends (like s3, gcs, b2, azureblob etc) if
diff --git a/fs/operations/lsjson.go b/fs/operations/lsjson.go
index 144a47bb6e13b..067681956747e 100644
--- a/fs/operations/lsjson.go
+++ b/fs/operations/lsjson.go
@@ -29,6 +29,7 @@ type ListJSONItem struct {
OrigID string `json:",omitempty"`
Tier string `json:",omitempty"`
IsBucket bool `json:",omitempty"`
+ Metadata fs.Metadata `json:",omitempty"`
}
// Timestamp a time in the provided format
@@ -80,6 +81,7 @@ type ListJSONOpt struct {
ShowHash bool `json:"showHash"`
DirsOnly bool `json:"dirsOnly"`
FilesOnly bool `json:"filesOnly"`
+ Metadata bool `json:"metadata"`
HashTypes []string `json:"hashTypes"` // hash types to show if ShowHash is set, e.g. "MD5", "SHA-1"
}
@@ -222,6 +224,14 @@ func (lj *listJSON) entry(ctx context.Context, entry fs.DirEntry) (*ListJSONItem
item.Tier = do.GetTier()
}
}
+ if lj.opt.Metadata {
+ metadata, err := fs.GetMetadata(ctx, x)
+ if err != nil {
+ fs.Errorf(x, "Failed to read metadata: %v", err)
+ } else if metadata != nil {
+ item.Metadata = metadata
+ }
+ }
default:
fs.Errorf(nil, "Unknown type %T in listing in ListJSON", entry)
}
diff --git a/fs/operations/lsjson_test.go b/fs/operations/lsjson_test.go
index ebc2c3f479867..d5d7ca319c160 100644
--- a/fs/operations/lsjson_test.go
+++ b/fs/operations/lsjson_test.go
@@ -172,6 +172,19 @@ func TestListJSON(t *testing.T) {
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}},
+ }, {
+ name: "Metadata",
+ opt: operations.ListJSONOpt{
+ FilesOnly: true,
+ Metadata: true,
+ },
+ want: []*operations.ListJSONItem{{
+ Path: "file1",
+ Name: "file1",
+ Size: 5,
+ ModTime: operations.Timestamp{When: t1},
+ IsDir: false,
+ }},
},
} {
t.Run(test.name, func(t *testing.T) {
diff --git a/fs/operations/rc.go b/fs/operations/rc.go
index bfa7e2599133d..39705b11958a5 100644
--- a/fs/operations/rc.go
+++ b/fs/operations/rc.go
@@ -34,6 +34,7 @@ func init() {
- noMimeType - If set don't show mime types
- dirsOnly - If set only show directories
- filesOnly - If set only show files
+ - metadata - If set return metadata of objects also
- hashTypes - array of strings of hash types to show if showHash set
Returns:
From 776a08389244335ad05d5b9a0825c1f4ade555e1 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 24 May 2022 11:31:48 +0100
Subject: [PATCH 121/560] lsf: add metadata support with `M` flag
---
cmd/lsf/lsf.go | 4 ++++
fs/operations/operations.go | 16 ++++++++++++++++
fs/operations/operations_test.go | 5 +++++
3 files changed, 25 insertions(+)
diff --git a/cmd/lsf/lsf.go b/cmd/lsf/lsf.go
index 732d2b5e8980a..e370f930c2b48 100644
--- a/cmd/lsf/lsf.go
+++ b/cmd/lsf/lsf.go
@@ -72,6 +72,7 @@ output:
m - MimeType of object if known
e - encrypted name
T - tier of storage if known, e.g. "Hot" or "Cool"
+ M - Metadata of object in JSON blob format, eg {"key":"value"}
So if you wanted the path, size and modification time, you would use
` + "`--format \"pst\"`, or maybe `--format \"tsp\"`" + ` to put the path last.
@@ -198,6 +199,9 @@ func Lsf(ctx context.Context, fsrc fs.Fs, out io.Writer) error {
opt.ShowOrigIDs = true
case 'T':
list.AddTier()
+ case 'M':
+ list.AddMetadata()
+ opt.Metadata = true
default:
return fmt.Errorf("unknown format character %q", char)
}
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index a41a0a009a160..1ea41853fbe6d 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -7,6 +7,7 @@ import (
"encoding/base64"
"encoding/csv"
"encoding/hex"
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -2154,6 +2155,21 @@ func (l *ListFormat) AddMimeType() {
})
}
+// AddMetadata adds file's Metadata to the output if known
+func (l *ListFormat) AddMetadata() {
+ l.AppendOutput(func(entry *ListJSONItem) string {
+ metadata := entry.Metadata
+ if metadata == nil {
+ metadata = make(fs.Metadata)
+ }
+ out, err := json.Marshal(metadata)
+ if err != nil {
+ return fmt.Sprintf("Failed to read metadata: %v", err.Error())
+ }
+ return string(out)
+ })
+}
+
// AppendOutput adds string generated by specific function to printed output
func (l *ListFormat) AppendOutput(functionToAppend func(item *ListJSONItem) string) {
l.output = append(l.output, functionToAppend)
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 4b65f689409cb..8cd7296e4c2b2 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -1428,6 +1428,11 @@ func TestListFormat(t *testing.T) {
assert.Contains(t, list.Format(item0), "/")
assert.Equal(t, "inode/directory", list.Format(item1))
+ list.SetOutput(nil)
+ list.AddMetadata()
+ assert.Equal(t, "{}", list.Format(item0))
+ assert.Equal(t, "{}", list.Format(item1))
+
list.SetOutput(nil)
list.AddPath()
list.SetAbsolute(true)
From a692bd2cd45d5f4ae8799808bc4e2267c96ec94c Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 22 Jun 2022 15:40:30 +0100
Subject: [PATCH 122/560] s3: change metadata storage to normal map with
lowercase keys
---
backend/s3/s3.go | 58 +++++++++++++++++++++++++++++++-----------------
1 file changed, 38 insertions(+), 20 deletions(-)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index c5dfbcb74c25d..7a28982640fad 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -2072,14 +2072,14 @@ type Object struct {
//
// List will read everything but meta & mimeType - to fill
// that in you need to call readMetaData
- fs *Fs // what this object is part of
- remote string // The remote path
- md5 string // md5sum of the object
- bytes int64 // size of the object
- lastModified time.Time // Last modified
- meta map[string]*string // The object metadata if known - may be nil
- mimeType string // MimeType of object - may be ""
- storageClass string // e.g. GLACIER
+ fs *Fs // what this object is part of
+ remote string // The remote path
+ md5 string // md5sum of the object
+ bytes int64 // size of the object
+ lastModified time.Time // Last modified
+ meta map[string]string // The object metadata if known - may be nil - with lower case keys
+ mimeType string // MimeType of object - may be ""
+ storageClass string // e.g. GLACIER
}
// ------------------------------------------------------------
@@ -3753,6 +3753,27 @@ func (o *Object) readMetaData(ctx context.Context) (err error) {
return nil
}
+// Convert S3 metadata with pointers into a map[string]string
+// while lowercasing the keys
+func s3MetadataToMap(s3Meta map[string]*string) map[string]string {
+ meta := make(map[string]string, len(s3Meta))
+ for k, v := range s3Meta {
+ if v != nil {
+ meta[strings.ToLower(k)] = *v
+ }
+ }
+ return meta
+}
+
+// Convert our metadata back into S3 metadata
+func mapToS3Metadata(meta map[string]string) map[string]*string {
+ s3Meta := make(map[string]*string, len(meta))
+ for k, v := range meta {
+ s3Meta[k] = aws.String(v)
+ }
+ return s3Meta
+}
+
func (o *Object) setMetaData(etag *string, contentLength *int64, lastModified *time.Time, meta map[string]*string, mimeType *string, storageClass *string) {
// Ignore missing Content-Length assuming it is 0
// Some versions of ceph do this due their apache proxies
@@ -3760,17 +3781,14 @@ func (o *Object) setMetaData(etag *string, contentLength *int64, lastModified *t
o.bytes = *contentLength
}
o.setMD5FromEtag(aws.StringValue(etag))
- o.meta = meta
- if o.meta == nil {
- o.meta = map[string]*string{}
- }
+ o.meta = s3MetadataToMap(meta)
// Read MD5 from metadata if present
if md5sumBase64, ok := o.meta[metaMD5Hash]; ok {
- md5sumBytes, err := base64.StdEncoding.DecodeString(*md5sumBase64)
+ md5sumBytes, err := base64.StdEncoding.DecodeString(md5sumBase64)
if err != nil {
- fs.Debugf(o, "Failed to read md5sum from metadata %q: %v", *md5sumBase64, err)
+ fs.Debugf(o, "Failed to read md5sum from metadata %q: %v", md5sumBase64, err)
} else if len(md5sumBytes) != 16 {
- fs.Debugf(o, "Failed to read md5sum from metadata %q: wrong length", *md5sumBase64)
+ fs.Debugf(o, "Failed to read md5sum from metadata %q: wrong length", md5sumBase64)
} else {
o.md5 = hex.EncodeToString(md5sumBytes)
}
@@ -3800,11 +3818,11 @@ func (o *Object) ModTime(ctx context.Context) time.Time {
}
// read mtime out of metadata if available
d, ok := o.meta[metaMtime]
- if !ok || d == nil {
+ if !ok {
// fs.Debugf(o, "No metadata")
return o.lastModified
}
- modTime, err := swift.FloatStringToTime(*d)
+ modTime, err := swift.FloatStringToTime(d)
if err != nil {
fs.Logf(o, "Failed to read mtime from object: %v", err)
return o.lastModified
@@ -3818,7 +3836,7 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
if err != nil {
return err
}
- o.meta[metaMtime] = aws.String(swift.TimeToFloatString(modTime))
+ o.meta[metaMtime] = swift.TimeToFloatString(modTime)
// Can't update metadata here, so return this error to force a recopy
if o.storageClass == "GLACIER" || o.storageClass == "DEEP_ARCHIVE" {
@@ -3829,7 +3847,7 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
bucket, bucketPath := o.split()
req := s3.CopyObjectInput{
ContentType: aws.String(fs.MimeType(ctx, o)), // Guess the content type
- Metadata: o.meta,
+ Metadata: mapToS3Metadata(o.meta),
MetadataDirective: aws.String(s3.MetadataDirectiveReplace), // replace metadata with that passed in
}
if o.fs.opt.RequesterPays {
@@ -4423,7 +4441,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
o.md5 = md5sumHex
o.bytes = size
o.lastModified = time.Now()
- o.meta = req.Metadata
+ o.meta = s3MetadataToMap(req.Metadata)
o.mimeType = aws.StringValue(req.ContentType)
o.storageClass = aws.StringValue(req.StorageClass)
// If we have done a single part PUT request then we can read these
From 22abd785eba35ca29cfcd6c162ca46e933344a1a Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 24 May 2022 12:32:39 +0100
Subject: [PATCH 123/560] s3: implement reading and writing of metadata #111
---
backend/s3/s3.go | 275 +++++++++++++++++++++++++++------
backend/s3/s3_internal_test.go | 61 ++++++++
docs/content/s3.md | 20 +++
3 files changed, 307 insertions(+), 49 deletions(-)
create mode 100644 backend/s3/s3_internal_test.go
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index 7a28982640fad..1693446b08640 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -71,6 +71,10 @@ func init() {
}
return nil, fmt.Errorf("unknown state %q", config.State)
},
+ MetadataInfo: &fs.MetadataInfo{
+ System: systemMetadataInfo,
+ Help: `User metadata is stored as x-amz-meta- keys. S3 metadata keys are case insensitive and are always returned in lower case.`,
+ },
Options: []fs.Option{{
Name: fs.ConfigProvider,
Help: "Choose your S3 provider.",
@@ -1984,8 +1988,8 @@ circumstances or for testing.
// Constants
const (
- metaMtime = "Mtime" // the meta key to store mtime in - e.g. X-Amz-Meta-Mtime
- metaMD5Hash = "Md5chksum" // the meta key to store md5hash in
+ metaMtime = "mtime" // the meta key to store mtime in - e.g. X-Amz-Meta-Mtime
+ metaMD5Hash = "md5chksum" // the meta key to store md5hash in
// The maximum size of object we can COPY - this should be 5 GiB but is < 5 GB for b2 compatibility
// See https://forum.rclone.org/t/copying-files-within-a-b2-bucket/16680/76
maxSizeForCopy = 4768 * 1024 * 1024
@@ -2000,6 +2004,57 @@ const (
maxExpireDuration = fs.Duration(7 * 24 * time.Hour) // max expiry is 1 week
)
+// system metadata keys which this backend owns
+var systemMetadataInfo = map[string]fs.MetadataHelp{
+ "cache-control": {
+ Help: "Cache-Control header",
+ Type: "string",
+ Example: "no-cache",
+ },
+ "content-disposition": {
+ Help: "Content-Disposition header",
+ Type: "string",
+ Example: "inline",
+ },
+ "content-encoding": {
+ Help: "Content-Encoding header",
+ Type: "string",
+ Example: "gzip",
+ },
+ "content-language": {
+ Help: "Content-Language header",
+ Type: "string",
+ Example: "en-US",
+ },
+ "content-type": {
+ Help: "Content-Type header",
+ Type: "string",
+ Example: "text/plain",
+ },
+ // "tagging": {
+ // Help: "x-amz-tagging header",
+ // Type: "string",
+ // Example: "tag1=value1&tag2=value2",
+ // },
+ "tier": {
+ Help: "Tier of the object",
+ Type: "string",
+ Example: "GLACIER",
+ ReadOnly: true,
+ },
+ "mtime": {
+ Help: "Time of last modification, read from rclone metadata",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z07:00",
+ },
+ "btime": {
+ Help: "Time of file birth (creation) read from Last-Modified header",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z07:00",
+ ReadOnly: true,
+ },
+}
+
// Options defines the configuration for this backend
type Options struct {
Provider string `config:"provider"`
@@ -2079,7 +2134,13 @@ type Object struct {
lastModified time.Time // Last modified
meta map[string]string // The object metadata if known - may be nil - with lower case keys
mimeType string // MimeType of object - may be ""
- storageClass string // e.g. GLACIER
+
+ // Metadata as pointers to strings as they often won't be present
+ storageClass *string // e.g. GLACIER
+ cacheControl *string // Cache-Control: header
+ contentDisposition *string // Content-Disposition: header
+ contentEncoding *string // Content-Encoding: header
+ contentLanguage *string // Content-Language: header
}
// ------------------------------------------------------------
@@ -2573,6 +2634,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
f.features = (&fs.Features{
ReadMimeType: true,
WriteMimeType: true,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: true,
BucketBased: true,
BucketBasedRootOK: true,
SetTier: true,
@@ -2623,7 +2687,7 @@ func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *s3.Obje
}
o.setMD5FromEtag(aws.StringValue(info.ETag))
o.bytes = aws.Int64Value(info.Size)
- o.storageClass = aws.StringValue(info.StorageClass)
+ o.storageClass = info.StorageClass
} else if !o.fs.opt.NoHeadObject {
err := o.readMetaData(ctx) // reads info and meta, returning an error
if err != nil {
@@ -3491,7 +3555,7 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
st.Status = "Not an S3 object"
return
}
- if o.storageClass != "GLACIER" && o.storageClass != "DEEP_ARCHIVE" {
+ if o.storageClass == nil || (*o.storageClass != "GLACIER" && *o.storageClass != "DEEP_ARCHIVE") {
st.Status = "Not GLACIER or DEEP_ARCHIVE storage class"
return
}
@@ -3749,7 +3813,8 @@ func (o *Object) readMetaData(ctx context.Context) (err error) {
if err != nil {
return err
}
- o.setMetaData(resp.ETag, resp.ContentLength, resp.LastModified, resp.Metadata, resp.ContentType, resp.StorageClass)
+ o.setMetaData(resp)
+ // resp.ETag, resp.ContentLength, resp.LastModified, resp.Metadata, resp.ContentType, resp.StorageClass)
return nil
}
@@ -3774,14 +3839,14 @@ func mapToS3Metadata(meta map[string]string) map[string]*string {
return s3Meta
}
-func (o *Object) setMetaData(etag *string, contentLength *int64, lastModified *time.Time, meta map[string]*string, mimeType *string, storageClass *string) {
+func (o *Object) setMetaData(resp *s3.HeadObjectOutput) {
// Ignore missing Content-Length assuming it is 0
// Some versions of ceph do this due their apache proxies
- if contentLength != nil {
- o.bytes = *contentLength
+ if resp.ContentLength != nil {
+ o.bytes = *resp.ContentLength
}
- o.setMD5FromEtag(aws.StringValue(etag))
- o.meta = s3MetadataToMap(meta)
+ o.setMD5FromEtag(aws.StringValue(resp.ETag))
+ o.meta = s3MetadataToMap(resp.Metadata)
// Read MD5 from metadata if present
if md5sumBase64, ok := o.meta[metaMD5Hash]; ok {
md5sumBytes, err := base64.StdEncoding.DecodeString(md5sumBase64)
@@ -3793,14 +3858,20 @@ func (o *Object) setMetaData(etag *string, contentLength *int64, lastModified *t
o.md5 = hex.EncodeToString(md5sumBytes)
}
}
- o.storageClass = aws.StringValue(storageClass)
- if lastModified == nil {
+ if resp.LastModified == nil {
o.lastModified = time.Now()
fs.Logf(o, "Failed to read last modified")
} else {
- o.lastModified = *lastModified
+ o.lastModified = *resp.LastModified
}
- o.mimeType = aws.StringValue(mimeType)
+ o.mimeType = aws.StringValue(resp.ContentType)
+
+ // Set system metadata
+ o.storageClass = resp.StorageClass
+ o.cacheControl = resp.CacheControl
+ o.contentDisposition = resp.ContentDisposition
+ o.contentEncoding = resp.ContentEncoding
+ o.contentLanguage = resp.ContentLanguage
}
// ModTime returns the modification time of the object
@@ -3839,7 +3910,7 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
o.meta[metaMtime] = swift.TimeToFloatString(modTime)
// Can't update metadata here, so return this error to force a recopy
- if o.storageClass == "GLACIER" || o.storageClass == "DEEP_ARCHIVE" {
+ if o.storageClass != nil && (*o.storageClass == "GLACIER" || *o.storageClass == "DEEP_ARCHIVE") {
return fs.ErrorCantSetModTime
}
@@ -3900,17 +3971,34 @@ func (o *Object) downloadFromURL(ctx context.Context, bucketPath string, options
metaData := make(map[string]*string)
for key, value := range resp.Header {
- if strings.HasPrefix(key, "x-amz-meta") {
+ key = strings.ToLower(key)
+ if strings.HasPrefix(key, "x-amz-meta-") {
metaKey := strings.TrimPrefix(key, "x-amz-meta-")
- metaData[strings.Title(metaKey)] = &value[0]
+ metaData[metaKey] = &value[0]
}
}
- storageClass := resp.Header.Get("X-Amz-Storage-Class")
- contentType := resp.Header.Get("Content-Type")
- etag := resp.Header.Get("Etag")
-
- o.setMetaData(&etag, contentLength, &lastModified, metaData, &contentType, &storageClass)
+ header := func(k string) *string {
+ v := resp.Header.Get(k)
+ if v == "" {
+ return nil
+ }
+ return &v
+ }
+
+ var head = s3.HeadObjectOutput{
+ ETag: header("Etag"),
+ ContentLength: contentLength,
+ LastModified: &lastModified,
+ Metadata: metaData,
+ CacheControl: header("Cache-Control"),
+ ContentDisposition: header("Content-Disposition"),
+ ContentEncoding: header("Content-Encoding"),
+ ContentLanguage: header("Content-Language"),
+ ContentType: header("Content-Type"),
+ StorageClass: header("X-Amz-Storage-Class"),
+ }
+ o.setMetaData(&head)
return resp.Body, err
}
@@ -3985,7 +4073,10 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
fs.Debugf(o, "Failed to find length in %q", contentRange)
}
}
- o.setMetaData(resp.ETag, size, resp.LastModified, resp.Metadata, resp.ContentType, resp.StorageClass)
+ var head s3.HeadObjectOutput
+ structs.SetFrom(&head, resp)
+ head.ContentLength = size
+ o.setMetaData(&head)
return resp.Body, nil
}
@@ -4322,11 +4413,56 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
multipart := size < 0 || size >= int64(o.fs.opt.UploadCutoff)
- // Set the mtime in the meta data
- metadata := map[string]*string{
- metaMtime: aws.String(swift.TimeToFloatString(modTime)),
+ req := s3.PutObjectInput{
+ Bucket: &bucket,
+ ACL: &o.fs.opt.ACL,
+ Key: &bucketPath,
+ }
+
+ // Fetch metadata if --metadata is in use
+ meta, err := fs.GetMetadataOptions(ctx, src, options)
+ if err != nil {
+ return fmt.Errorf("failed to read metadata from source object: %w", err)
+ }
+ req.Metadata = make(map[string]*string, len(meta)+2)
+ // merge metadata into request and user metadata
+ for k, v := range meta {
+ pv := aws.String(v)
+ k = strings.ToLower(k)
+ switch k {
+ case "cache-control":
+ req.CacheControl = pv
+ case "content-disposition":
+ req.ContentDisposition = pv
+ case "content-encoding":
+ req.ContentEncoding = pv
+ case "content-language":
+ req.ContentLanguage = pv
+ case "content-type":
+ req.ContentType = pv
+ case "x-amz-tagging":
+ req.Tagging = pv
+ case "tier":
+ // ignore
+ case "mtime":
+ // mtime in meta overrides source ModTime
+ metaModTime, err := time.Parse(time.RFC3339Nano, v)
+ if err != nil {
+ fs.Debugf(o, "failed to parse metadata %s: %q: %v", k, v, err)
+ } else {
+ modTime = metaModTime
+ }
+ case "btime":
+ // write as metadata since we can't set it
+ req.Metadata[k] = pv
+ default:
+ req.Metadata[k] = pv
+ }
}
+ // Set the mtime in the meta data
+ req.Metadata[metaMtime] = aws.String(swift.TimeToFloatString(modTime))
+
// read the md5sum if available
// - for non multipart
// - so we can add a ContentMD5
@@ -4346,20 +4482,15 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
// - a multipart upload
// - the Etag is not an MD5, eg when using SSE/SSE-C
// provided checksums aren't disabled
- metadata[metaMD5Hash] = &md5sumBase64
+ req.Metadata[metaMD5Hash] = &md5sumBase64
}
}
}
}
- // Guess the content type
- mimeType := fs.MimeType(ctx, src)
- req := s3.PutObjectInput{
- Bucket: &bucket,
- ACL: &o.fs.opt.ACL,
- Key: &bucketPath,
- ContentType: &mimeType,
- Metadata: metadata,
+ // Set the content type it it isn't set already
+ if req.ContentType == nil {
+ req.ContentType = aws.String(fs.MimeType(ctx, src))
}
if size >= 0 {
req.ContentLength = &size
@@ -4438,19 +4569,19 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
// so make up the object as best we can assuming it got
// uploaded properly. If size < 0 then we need to do the HEAD.
if o.fs.opt.NoHead && size >= 0 {
- o.md5 = md5sumHex
- o.bytes = size
- o.lastModified = time.Now()
- o.meta = s3MetadataToMap(req.Metadata)
- o.mimeType = aws.StringValue(req.ContentType)
- o.storageClass = aws.StringValue(req.StorageClass)
+ var head s3.HeadObjectOutput
+ structs.SetFrom(&head, req)
+ head.ETag = &md5sumHex // doesn't matter quotes are misssing
+ head.ContentLength = &size
// If we have done a single part PUT request then we can read these
if gotEtag != "" {
- o.setMD5FromEtag(gotEtag)
+ head.ETag = &gotEtag
}
- if !o.lastModified.IsZero() {
- o.lastModified = lastModified
+ if lastModified.IsZero() {
+ lastModified = time.Now()
}
+ head.LastModified = &lastModified
+ o.setMetaData(&head)
return nil
}
@@ -4460,7 +4591,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
if err != nil {
return err
}
- o.setMetaData(head.ETag, head.ContentLength, head.LastModified, head.Metadata, head.ContentType, head.StorageClass)
+ o.setMetaData(head)
if o.fs.opt.UseMultipartEtag.Value && !o.fs.etagIsNotMD5 && wantETag != "" && head.ETag != nil && *head.ETag != "" {
gotETag := strings.Trim(strings.ToLower(*head.ETag), `"`)
if wantETag != gotETag {
@@ -4511,16 +4642,61 @@ func (o *Object) SetTier(tier string) (err error) {
if err != nil {
return err
}
- o.storageClass = tier
+ o.storageClass = &tier
return err
}
// GetTier returns storage class as string
func (o *Object) GetTier() string {
- if o.storageClass == "" {
+ if o.storageClass == nil || *o.storageClass == "" {
return "STANDARD"
}
- return o.storageClass
+ return *o.storageClass
+}
+
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *Object) Metadata(ctx context.Context) (metadata fs.Metadata, err error) {
+ err = o.readMetaData(ctx)
+ if err != nil {
+ return nil, err
+ }
+ metadata = make(fs.Metadata, len(o.meta)+7)
+ for k, v := range o.meta {
+ switch k {
+ case metaMtime:
+ if modTime, err := swift.FloatStringToTime(v); err == nil {
+ metadata["mtime"] = modTime.Format(time.RFC3339Nano)
+ }
+ case metaMD5Hash:
+ // don't write hash metadata
+ default:
+ metadata[k] = v
+ }
+ }
+ if o.mimeType != "" {
+ metadata["content-type"] = o.mimeType
+ }
+ // metadata["x-amz-tagging"] = ""
+ if !o.lastModified.IsZero() {
+ metadata["btime"] = o.lastModified.Format(time.RFC3339Nano)
+ }
+
+ // Set system metadata
+ setMetadata := func(k string, v *string) {
+ if v == nil || *v == "" {
+ return
+ }
+ metadata[k] = *v
+ }
+ setMetadata("cache-control", o.cacheControl)
+ setMetadata("content-disposition", o.contentDisposition)
+ setMetadata("content-encoding", o.contentEncoding)
+ setMetadata("content-language", o.contentLanguage)
+ setMetadata("tier", o.storageClass)
+
+ return metadata, nil
}
// Check the interfaces are satisfied
@@ -4535,4 +4711,5 @@ var (
_ fs.MimeTyper = &Object{}
_ fs.GetTierer = &Object{}
_ fs.SetTierer = &Object{}
+ _ fs.Metadataer = &Object{}
)
diff --git a/backend/s3/s3_internal_test.go b/backend/s3/s3_internal_test.go
new file mode 100644
index 0000000000000..ecd39f41f3ec8
--- /dev/null
+++ b/backend/s3/s3_internal_test.go
@@ -0,0 +1,61 @@
+package s3
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fstest"
+ "github.com/rclone/rclone/fstest/fstests"
+ "github.com/rclone/rclone/lib/random"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func (f *Fs) InternalTestMetadata(t *testing.T) {
+ ctx := context.Background()
+ contents := random.String(100)
+ item := fstest.NewItem("test-metadata", contents, fstest.Time("2001-05-06T04:05:06.499999999Z"))
+ btime := time.Now()
+ metadata := fs.Metadata{
+ "cache-control": "no-cache",
+ "content-disposition": "inline",
+ "content-encoding": "gzip",
+ "content-language": "en-US",
+ "content-type": "text/plain",
+ "mtime": "2009-05-06T04:05:06.499999999Z",
+ // "tier" - read only
+ // "btime" - read only
+ }
+ obj := fstests.PutTestContentsMetadata(ctx, t, f, &item, contents, true, "text/html", metadata)
+ defer func() {
+ assert.NoError(t, obj.Remove(ctx))
+ }()
+ o := obj.(*Object)
+ gotMetadata, err := o.Metadata(ctx)
+ require.NoError(t, err)
+ for k, v := range metadata {
+ got := gotMetadata[k]
+ switch k {
+ case "mtime":
+ assert.True(t, fstest.Time(v).Equal(fstest.Time(got)))
+ case "btime":
+ gotBtime := fstest.Time(got)
+ dt := gotBtime.Sub(btime)
+ assert.True(t, dt < time.Minute && dt > -time.Minute, fmt.Sprintf("btime more than 1 minute out want %v got %v delta %v", btime, gotBtime, dt))
+ assert.True(t, fstest.Time(v).Equal(fstest.Time(got)))
+ case "tier":
+ assert.NotEqual(t, "", got)
+ default:
+ assert.Equal(t, v, got, k)
+ }
+ }
+}
+
+func (f *Fs) InternalTest(t *testing.T) {
+ t.Run("Metadata", f.InternalTestMetadata)
+}
+
+var _ fstests.InternalTester = (*Fs)(nil)
diff --git a/docs/content/s3.md b/docs/content/s3.md
index c2adbfe95222c..ddf9fd23dec58 100644
--- a/docs/content/s3.md
+++ b/docs/content/s3.md
@@ -2369,6 +2369,26 @@ Properties:
- Type: Tristate
- Default: unset
+### Metadata
+
+User metadata is stored as x-amz-meta- keys. S3 metadata keys are case insensitive and are always returned in lower case.
+
+Here are the possible system metadata items for the s3 backend.
+
+| Name | Help | Type | Example | Read Only |
+|------|------|------|---------|-----------|
+| btime | Time of file birth (creation) read from Last-Modified header | RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 | **Y** |
+| cache-control | Cache-Control header | string | no-cache | N |
+| content-disposition | Content-Disposition header | string | inline | N |
+| content-encoding | Content-Encoding header | string | gzip | N |
+| content-language | Content-Language header | string | en-US | N |
+| content-type | Content-Type header | string | text/plain | N |
+| mtime | Time of last modification, read from rclone metadata | RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 | N |
+| tier | Tier of the object | string | GLACIER | **Y** |
+
+
+See the [metadata](/docs/#metadata) docs for more info.
+
## Backend commands
Here are the commands specific to the s3 backend.
From c556e98f496c0735fec487b4752e0c458420f331 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Tue, 24 May 2022 18:06:16 +0100
Subject: [PATCH 124/560] local: add Metadata support #111
---
backend/local/local.go | 71 ++++++++++++--
backend/local/local_internal_test.go | 137 ++++++++++++++++++++++++++
backend/local/metadata.go | 138 +++++++++++++++++++++++++++
backend/local/metadata_bsd.go | 38 ++++++++
backend/local/metadata_linux.go | 47 +++++++++
backend/local/metadata_other.go | 21 ++++
backend/local/metadata_unix.go | 37 +++++++
backend/local/metadata_windows.go | 34 +++++++
backend/local/setbtime.go | 16 ++++
backend/local/setbtime_windows.go | 28 ++++++
backend/local/xattr.go | 87 +++++++++++++++++
backend/local/xattr_unsupported.go | 21 ++++
docs/content/local.md | 26 +++++
go.mod | 1 +
go.sum | 3 +
15 files changed, 699 insertions(+), 6 deletions(-)
create mode 100644 backend/local/metadata.go
create mode 100644 backend/local/metadata_bsd.go
create mode 100644 backend/local/metadata_linux.go
create mode 100644 backend/local/metadata_other.go
create mode 100644 backend/local/metadata_unix.go
create mode 100644 backend/local/metadata_windows.go
create mode 100644 backend/local/setbtime.go
create mode 100644 backend/local/setbtime_windows.go
create mode 100644 backend/local/xattr.go
create mode 100644 backend/local/xattr_unsupported.go
diff --git a/backend/local/local.go b/backend/local/local.go
index 1a8908720c4f0..3a83bd2623442 100644
--- a/backend/local/local.go
+++ b/backend/local/local.go
@@ -42,6 +42,18 @@ func init() {
Description: "Local Disk",
NewFs: NewFs,
CommandHelp: commandHelp,
+ MetadataInfo: &fs.MetadataInfo{
+ System: systemMetadataInfo,
+ Help: `Depending on which OS is in use the local backend may return only some
+of the system metadata. Setting system metadata is supported on all
+OSes but setting user metadata is only supported on linux, freebsd,
+netbsd, macOS and Solaris. It is **not** supported on Windows yet
+([see pkg/attrs#47](https://github.com/pkg/xattr/issues/47)).
+
+User metadata is stored as extended attributes (which may not be
+supported by all file systems) under the "user.*" prefix.
+`,
+ },
Options: []fs.Option{{
Name: "nounc",
Help: "Disable UNC (long path names) conversion on Windows.",
@@ -280,6 +292,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
CanHaveEmptyDirectories: true,
IsLocal: true,
SlowHash: true,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: xattrSupported, // can only R/W general purpose metadata if xattrs are supported
}).Fill(ctx, f)
if opt.FollowSymlinks {
f.lstat = os.Stat
@@ -938,17 +953,22 @@ func (o *Object) ModTime(ctx context.Context) time.Time {
return o.modTime
}
+// Set the atime and ltime of the object
+func (o *Object) setTimes(atime, mtime time.Time) (err error) {
+ if o.translatedLink {
+ err = lChtimes(o.path, atime, mtime)
+ } else {
+ err = os.Chtimes(o.path, atime, mtime)
+ }
+ return err
+}
+
// SetModTime sets the modification time of the local fs object
func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
if o.fs.opt.NoSetModTime {
return nil
}
- var err error
- if o.translatedLink {
- err = lChtimes(o.path, modTime, modTime)
- } else {
- err = os.Chtimes(o.path, modTime, modTime)
- }
+ err := o.setTimes(modTime, modTime)
if err != nil {
return err
}
@@ -1223,6 +1243,16 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
return err
}
+ // Fetch and set metadata if --metadata is in use
+ meta, err := fs.GetMetadataOptions(ctx, src, options)
+ if err != nil {
+ return fmt.Errorf("failed to read metadata from source object: %w", err)
+ }
+ err = o.writeMetadata(meta)
+ if err != nil {
+ return fmt.Errorf("failed to set metadata: %w", err)
+ }
+
// ReRead info now that we have finished
return o.lstat()
}
@@ -1321,6 +1351,34 @@ func (o *Object) Remove(ctx context.Context) error {
return remove(o.path)
}
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *Object) Metadata(ctx context.Context) (metadata fs.Metadata, err error) {
+ metadata, err = o.getXattr()
+ if err != nil {
+ return nil, err
+ }
+ err = o.readMetadataFromFile(&metadata)
+ if err != nil {
+ return nil, err
+ }
+ return metadata, nil
+}
+
+// Write the metadata on the object
+func (o *Object) writeMetadata(metadata fs.Metadata) (err error) {
+ err = o.setXattr(metadata)
+ if err != nil {
+ return err
+ }
+ err = o.writeMetadataToFile(metadata)
+ if err != nil {
+ return err
+ }
+ return err
+}
+
func cleanRootPath(s string, noUNC bool, enc encoder.MultiEncoder) string {
if runtime.GOOS == "windows" {
if !filepath.IsAbs(s) && !strings.HasPrefix(s, "\\") {
@@ -1360,4 +1418,5 @@ var (
_ fs.Commander = &Fs{}
_ fs.OpenWriterAter = &Fs{}
_ fs.Object = &Object{}
+ _ fs.Metadataer = &Object{}
)
diff --git a/backend/local/local_internal_test.go b/backend/local/local_internal_test.go
index 2957dcd1cfd55..55b98bb53728b 100644
--- a/backend/local/local_internal_test.go
+++ b/backend/local/local_internal_test.go
@@ -3,10 +3,12 @@ package local
import (
"bytes"
"context"
+ "fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
+ "runtime"
"testing"
"time"
@@ -229,3 +231,138 @@ func TestHashOnDelete(t *testing.T) {
_, err = o.Hash(ctx, hash.MD5)
require.Error(t, err)
}
+
+func TestMetadata(t *testing.T) {
+ ctx := context.Background()
+ r := fstest.NewRun(t)
+ defer r.Finalise()
+ const filePath = "metafile.txt"
+ when := time.Now()
+ const dayLength = len("2001-01-01")
+ whenRFC := when.Format(time.RFC3339Nano)
+ r.WriteFile(filePath, "metadata file contents", when)
+ f := r.Flocal.(*Fs)
+
+ // Get the object
+ obj, err := f.NewObject(ctx, filePath)
+ require.NoError(t, err)
+ o := obj.(*Object)
+
+ features := f.Features()
+
+ var hasXID, hasAtime, hasBtime bool
+ switch runtime.GOOS {
+ case "darwin", "freebsd", "netbsd", "linux":
+ hasXID, hasAtime, hasBtime = true, true, true
+ case "openbsd", "solaris":
+ hasXID, hasAtime = true, true
+ case "windows":
+ hasAtime, hasBtime = true, true
+ case "plan9", "js":
+ // nada
+ default:
+ t.Errorf("No test cases for OS %q", runtime.GOOS)
+ }
+
+ assert.True(t, features.ReadMetadata)
+ assert.True(t, features.WriteMetadata)
+ assert.Equal(t, xattrSupported, features.UserMetadata)
+
+ t.Run("Xattr", func(t *testing.T) {
+ if !xattrSupported {
+ t.Skip()
+ }
+ m, err := o.getXattr()
+ require.NoError(t, err)
+ assert.Nil(t, m)
+
+ inM := fs.Metadata{
+ "potato": "chips",
+ "cabbage": "soup",
+ }
+ err = o.setXattr(inM)
+ require.NoError(t, err)
+
+ m, err = o.getXattr()
+ require.NoError(t, err)
+ assert.NotNil(t, m)
+ assert.Equal(t, inM, m)
+ })
+
+ checkTime := func(m fs.Metadata, key string, when time.Time) {
+ mt, ok := o.parseMetadataTime(m, key)
+ assert.True(t, ok)
+ dt := mt.Sub(when)
+ precision := time.Second
+ assert.True(t, dt >= -precision && dt <= precision, fmt.Sprintf("%s: dt %v outside +/- precision %v", key, dt, precision))
+ }
+
+ checkInt := func(m fs.Metadata, key string, base int) int {
+ value, ok := o.parseMetadataInt(m, key, base)
+ assert.True(t, ok)
+ return value
+ }
+ t.Run("Read", func(t *testing.T) {
+ m, err := o.Metadata(ctx)
+ require.NoError(t, err)
+ assert.NotNil(t, m)
+
+ // All OSes have these
+ checkInt(m, "mode", 8)
+ checkTime(m, "mtime", when)
+
+ assert.Equal(t, len(whenRFC), len(m["mtime"]))
+ assert.Equal(t, whenRFC[:dayLength], m["mtime"][:dayLength])
+
+ if hasAtime {
+ checkTime(m, "atime", when)
+ }
+ if hasBtime {
+ checkTime(m, "btime", when)
+ }
+ if hasXID {
+ checkInt(m, "uid", 10)
+ checkInt(m, "gid", 10)
+ }
+ })
+
+ t.Run("Write", func(t *testing.T) {
+ newAtimeString := "2011-12-13T14:15:16.999999999Z"
+ newAtime := fstest.Time(newAtimeString)
+ newMtimeString := "2011-12-12T14:15:16.999999999Z"
+ newMtime := fstest.Time(newMtimeString)
+ newBtimeString := "2011-12-11T14:15:16.999999999Z"
+ newBtime := fstest.Time(newBtimeString)
+ newM := fs.Metadata{
+ "mtime": newMtimeString,
+ "atime": newAtimeString,
+ "btime": newBtimeString,
+ // Can't test uid, gid without being root
+ "mode": "0767",
+ "potato": "wedges",
+ }
+ err := o.writeMetadata(newM)
+ require.NoError(t, err)
+
+ m, err := o.Metadata(ctx)
+ require.NoError(t, err)
+ assert.NotNil(t, m)
+
+ mode := checkInt(m, "mode", 8)
+ if runtime.GOOS != "windows" {
+ assert.Equal(t, 0767, mode&0777, fmt.Sprintf("mode wrong - expecting 0767 got 0%o", mode&0777))
+ }
+
+ checkTime(m, "mtime", newMtime)
+ if hasAtime {
+ checkTime(m, "atime", newAtime)
+ }
+ if haveSetBTime {
+ checkTime(m, "btime", newBtime)
+ }
+ if xattrSupported {
+ assert.Equal(t, "wedges", m["potato"])
+ }
+ })
+
+}
diff --git a/backend/local/metadata.go b/backend/local/metadata.go
new file mode 100644
index 0000000000000..097abedf18ffc
--- /dev/null
+++ b/backend/local/metadata.go
@@ -0,0 +1,138 @@
+package local
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+)
+
+const metadataTimeFormat = time.RFC3339Nano
+
+// system metadata keys which this backend owns
+//
+// not all values supported on all OSes
+var systemMetadataInfo = map[string]fs.MetadataHelp{
+ "mode": {
+ Help: "File type and mode",
+ Type: "octal, unix style",
+ Example: "0100664",
+ },
+ "uid": {
+ Help: "User ID of owner",
+ Type: "decimal number",
+ Example: "500",
+ },
+ "gid": {
+ Help: "Group ID of owner",
+ Type: "decimal number",
+ Example: "500",
+ },
+ "rdev": {
+ Help: "Device ID (if special file)",
+ Type: "hexadecimal",
+ Example: "1abc",
+ },
+ "atime": {
+ Help: "Time of last access",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z07:00",
+ },
+ "mtime": {
+ Help: "Time of last modification",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z07:00",
+ },
+ "btime": {
+ Help: "Time of file birth (creation)",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z07:00",
+ },
+}
+
+// parse a time string from metadata with key
+func (o *Object) parseMetadataTime(m fs.Metadata, key string) (t time.Time, ok bool) {
+ value, ok := m[key]
+ if ok {
+ var err error
+ t, err = time.Parse(metadataTimeFormat, value)
+ if err != nil {
+ fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
+ ok = false
+ }
+ }
+ return t, ok
+}
+
+// parse am int from metadata with key and base
+func (o *Object) parseMetadataInt(m fs.Metadata, key string, base int) (result int, ok bool) {
+ value, ok := m[key]
+ if ok {
+ var err error
+ result64, err := strconv.ParseInt(value, base, 64)
+ if err != nil {
+ fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
+ ok = false
+ }
+ result = int(result64)
+ }
+ return result, ok
+}
+
+// Write the metadata into the file
+//
+// It isn't possible to set the ctime and btime under Unix
+func (o *Object) writeMetadataToFile(m fs.Metadata) (outErr error) {
+ var err error
+ atime, atimeOK := o.parseMetadataTime(m, "atime")
+ mtime, mtimeOK := o.parseMetadataTime(m, "mtime")
+ btime, btimeOK := o.parseMetadataTime(m, "btime")
+ if atimeOK || mtimeOK {
+ if atimeOK && !mtimeOK {
+ mtime = atime
+ }
+ if !atimeOK && mtimeOK {
+ atime = mtime
+ }
+ err = o.setTimes(atime, mtime)
+ if err != nil {
+ outErr = fmt.Errorf("failed to set times: %w", err)
+ }
+ }
+ if haveSetBTime {
+ if btimeOK {
+ err = setBTime(o.path, btime)
+ if err != nil {
+ outErr = fmt.Errorf("failed to set birth (creation) time: %w", err)
+ }
+ }
+ }
+ uid, hasUID := o.parseMetadataInt(m, "uid", 10)
+ gid, hasGID := o.parseMetadataInt(m, "gid", 10)
+ if hasUID {
+ // FIXME should read UID and GID of current user and only attempt to set it if different
+ if !hasGID {
+ gid = uid
+ }
+ if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
+ fs.Debugf(o, "Ignoring request to set ownership %o.%o on this OS", gid, uid)
+ } else {
+ err = os.Chown(o.path, uid, gid)
+ if err != nil {
+ outErr = fmt.Errorf("failed to change ownership: %w", err)
+ }
+ }
+ }
+ mode, hasMode := o.parseMetadataInt(m, "mode", 8)
+ if hasMode {
+ err = os.Chmod(o.path, os.FileMode(mode))
+ if err != nil {
+ outErr = fmt.Errorf("failed to change permissions: %w", err)
+ }
+ }
+ // FIXME not parsing rdev yet
+ return outErr
+}
diff --git a/backend/local/metadata_bsd.go b/backend/local/metadata_bsd.go
new file mode 100644
index 0000000000000..c4c90d2266221
--- /dev/null
+++ b/backend/local/metadata_bsd.go
@@ -0,0 +1,38 @@
+//go:build darwin || freebsd || netbsd
+// +build darwin freebsd netbsd
+
+package local
+
+import (
+ "fmt"
+ "syscall"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+)
+
+// Read the metadata from the file into metadata where possible
+func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) {
+ info, err := o.fs.lstat(o.path)
+ if err != nil {
+ return err
+ }
+ stat, ok := info.Sys().(*syscall.Stat_t)
+ if !ok {
+ fs.Debugf(o, "didn't return Stat_t as expected")
+ return nil
+ }
+ m.Set("mode", fmt.Sprintf("%0o", stat.Mode))
+ m.Set("uid", fmt.Sprintf("%d", stat.Uid))
+ m.Set("gid", fmt.Sprintf("%d", stat.Gid))
+ if stat.Rdev != 0 {
+ m.Set("rdev", fmt.Sprintf("%x", stat.Rdev))
+ }
+ setTime := func(key string, t syscall.Timespec) {
+ m.Set(key, time.Unix(t.Unix()).Format(metadataTimeFormat))
+ }
+ setTime("atime", stat.Atimespec)
+ setTime("mtime", stat.Mtimespec)
+ setTime("btime", stat.Birthtimespec)
+ return nil
+}
diff --git a/backend/local/metadata_linux.go b/backend/local/metadata_linux.go
new file mode 100644
index 0000000000000..17d32c06bfde0
--- /dev/null
+++ b/backend/local/metadata_linux.go
@@ -0,0 +1,47 @@
+//go:build linux
+// +build linux
+
+package local
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+ "golang.org/x/sys/unix"
+)
+
+// Read the metadata from the file into metadata where possible
+func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) {
+ flags := unix.AT_SYMLINK_NOFOLLOW
+ if o.fs.opt.FollowSymlinks {
+ flags = 0
+ }
+ var stat unix.Statx_t
+ err = unix.Statx(unix.AT_FDCWD, o.path, flags, (0 |
+ unix.STATX_TYPE | // Want stx_mode & S_IFMT
+ unix.STATX_MODE | // Want stx_mode & ~S_IFMT
+ unix.STATX_UID | // Want stx_uid
+ unix.STATX_GID | // Want stx_gid
+ unix.STATX_ATIME | // Want stx_atime
+ unix.STATX_MTIME | // Want stx_mtime
+ unix.STATX_CTIME | // Want stx_ctime
+ unix.STATX_BTIME), // Want stx_btime
+ &stat)
+ if err != nil {
+ return err
+ }
+ m.Set("mode", fmt.Sprintf("%0o", stat.Mode))
+ m.Set("uid", fmt.Sprintf("%d", stat.Uid))
+ m.Set("gid", fmt.Sprintf("%d", stat.Gid))
+ if stat.Rdev_major != 0 || stat.Rdev_minor != 0 {
+ m.Set("rdev", fmt.Sprintf("%x", uint64(stat.Rdev_major)<<32|uint64(stat.Rdev_minor)))
+ }
+ setTime := func(key string, t unix.StatxTimestamp) {
+ m.Set(key, time.Unix(t.Sec, int64(t.Nsec)).Format(metadataTimeFormat))
+ }
+ setTime("atime", stat.Atime)
+ setTime("mtime", stat.Mtime)
+ setTime("btime", stat.Btime)
+ return nil
+}
diff --git a/backend/local/metadata_other.go b/backend/local/metadata_other.go
new file mode 100644
index 0000000000000..f2ce80956f6a6
--- /dev/null
+++ b/backend/local/metadata_other.go
@@ -0,0 +1,21 @@
+//go:build plan9 || js
+// +build plan9 js
+
+package local
+
+import (
+ "fmt"
+
+ "github.com/rclone/rclone/fs"
+)
+
+// Read the metadata from the file into metadata where possible
+func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) {
+ info, err := o.fs.lstat(o.path)
+ if err != nil {
+ return err
+ }
+ m.Set("mode", fmt.Sprintf("%0o", info.Mode()))
+ m.Set("mtime", info.ModTime().Format(metadataTimeFormat))
+ return nil
+}
diff --git a/backend/local/metadata_unix.go b/backend/local/metadata_unix.go
new file mode 100644
index 0000000000000..48bf7faa36a3f
--- /dev/null
+++ b/backend/local/metadata_unix.go
@@ -0,0 +1,37 @@
+//go:build openbsd || solaris
+// +build openbsd solaris
+
+package local
+
+import (
+ "fmt"
+ "syscall"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+)
+
+// Read the metadata from the file into metadata where possible
+func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) {
+ info, err := o.fs.lstat(o.path)
+ if err != nil {
+ return err
+ }
+ stat, ok := info.Sys().(*syscall.Stat_t)
+ if !ok {
+ fs.Debugf(o, "didn't return Stat_t as expected")
+ return nil
+ }
+ m.Set("mode", fmt.Sprintf("%0o", stat.Mode))
+ m.Set("uid", fmt.Sprintf("%d", stat.Uid))
+ m.Set("gid", fmt.Sprintf("%d", stat.Gid))
+ if stat.Rdev != 0 {
+ m.Set("rdev", fmt.Sprintf("%x", stat.Rdev))
+ }
+ setTime := func(key string, t syscall.Timespec) {
+ m.Set(key, time.Unix(t.Unix()).Format(metadataTimeFormat))
+ }
+ setTime("atime", stat.Atim)
+ setTime("mtime", stat.Mtim)
+ return nil
+}
diff --git a/backend/local/metadata_windows.go b/backend/local/metadata_windows.go
new file mode 100644
index 0000000000000..b2588953e95a9
--- /dev/null
+++ b/backend/local/metadata_windows.go
@@ -0,0 +1,34 @@
+//go:build windows
+// +build windows
+
+package local
+
+import (
+ "fmt"
+ "syscall"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+)
+
+// Read the metadata from the file into metadata where possible
+func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) {
+ info, err := o.fs.lstat(o.path)
+ if err != nil {
+ return err
+ }
+ stat, ok := info.Sys().(*syscall.Win32FileAttributeData)
+ if !ok {
+ fs.Debugf(o, "didn't return Win32FileAttributeData as expected")
+ return nil
+ }
+ // FIXME do something with stat.FileAttributes ?
+ m.Set("mode", fmt.Sprintf("%0o", info.Mode()))
+ setTime := func(key string, t syscall.Filetime) {
+ m.Set(key, time.Unix(0, t.Nanoseconds()).Format(metadataTimeFormat))
+ }
+ setTime("atime", stat.LastAccessTime)
+ setTime("mtime", stat.LastWriteTime)
+ setTime("btime", stat.CreationTime)
+ return nil
+}
diff --git a/backend/local/setbtime.go b/backend/local/setbtime.go
new file mode 100644
index 0000000000000..20a914bf2a38f
--- /dev/null
+++ b/backend/local/setbtime.go
@@ -0,0 +1,16 @@
+//go:build !windows
+// +build !windows
+
+package local
+
+import (
+ "time"
+)
+
+const haveSetBTime = false
+
+// setBTime changes the the birth time of the file passed in
+func setBTime(name string, btime time.Time) error {
+ // Does nothing
+ return nil
+}
diff --git a/backend/local/setbtime_windows.go b/backend/local/setbtime_windows.go
new file mode 100644
index 0000000000000..3734c2e7b5584
--- /dev/null
+++ b/backend/local/setbtime_windows.go
@@ -0,0 +1,28 @@
+//go:build windows
+// +build windows
+
+package local
+
+import (
+ "os"
+ "time"
+ "syscall"
+)
+
+const haveSetBTime = true
+
+// setBTime sets the the birth time of the file passed in
+func setBTime(name string, btime time.Time) (err error) {
+ h, err := syscall.Open(name, os.O_RDWR, 0755)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ closeErr := syscall.Close(h)
+ if err == nil {
+ err = closeErr
+ }
+ }()
+ bFileTime := syscall.NsecToFiletime(btime.UnixNano())
+ return syscall.SetFileTime(h, &bFileTime, nil, nil)
+}
diff --git a/backend/local/xattr.go b/backend/local/xattr.go
new file mode 100644
index 0000000000000..a2c0cc5e868e7
--- /dev/null
+++ b/backend/local/xattr.go
@@ -0,0 +1,87 @@
+//go:build !openbsd && !plan9
+// +build !openbsd,!plan9
+
+package local
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/pkg/xattr"
+ "github.com/rclone/rclone/fs"
+)
+
+const (
+ xattrPrefix = "user." // FIXME is this correct for all unixes?
+ xattrSupported = xattr.XATTR_SUPPORTED
+)
+
+// getXattr returns the extended attributes for an object
+//
+// It doesn't return any attributes owned by this backend in
+// metadataKeys
+func (o *Object) getXattr() (metadata fs.Metadata, err error) {
+ if !xattrSupported {
+ return nil, nil
+ }
+ var list []string
+ if o.fs.opt.FollowSymlinks {
+ list, err = xattr.List(o.path)
+ } else {
+ list, err = xattr.LList(o.path)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("failed to read xattr: %w", err)
+ }
+ if len(list) == 0 {
+ return nil, nil
+ }
+ metadata = make(fs.Metadata, len(list))
+ for _, k := range list {
+ var v []byte
+ if o.fs.opt.FollowSymlinks {
+ v, err = xattr.Get(o.path, k)
+ } else {
+ v, err = xattr.LGet(o.path, k)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("failed to read xattr key %q: %w", k, err)
+ }
+ k = strings.ToLower(k)
+ if !strings.HasPrefix(k, xattrPrefix) {
+ continue
+ }
+ k = k[len(xattrPrefix):]
+ if _, found := systemMetadataInfo[k]; found {
+ continue
+ }
+ metadata[k] = string(v)
+ }
+ return metadata, nil
+}
+
+// setXattr sets the metadata on the file Xattrs
+//
+// It doesn't set any attributes owned by this backend in metadataKeys
+func (o *Object) setXattr(metadata fs.Metadata) (err error) {
+ if !xattrSupported {
+ return nil
+ }
+ for k, value := range metadata {
+ k = strings.ToLower(k)
+ if _, found := systemMetadataInfo[k]; found {
+ continue
+ }
+ k = xattrPrefix + k
+ v := []byte(value)
+ if o.fs.opt.FollowSymlinks {
+ err = xattr.Set(o.path, k, v)
+ } else {
+ err = xattr.LSet(o.path, k, v)
+ }
+ if err != nil {
+ return fmt.Errorf("failed to set xattr key %q: %w", k, err)
+ }
+ }
+ return nil
+}
diff --git a/backend/local/xattr_unsupported.go b/backend/local/xattr_unsupported.go
new file mode 100644
index 0000000000000..88455939a2281
--- /dev/null
+++ b/backend/local/xattr_unsupported.go
@@ -0,0 +1,21 @@
+//go:build openbsd || plan9
+// +build openbsd plan9
+
+// The pkg/xattr module doesn't compile for openbsd or plan9
+package local
+
+import "github.com/rclone/rclone/fs"
+
+const (
+ xattrSupported = false
+)
+
+// getXattr returns the extended attributes for an object
+func (o *Object) getXattr() (metadata fs.Metadata, err error) {
+ return nil, nil
+}
+
+// setXattr sets the metadata on the file Xattrs
+func (o *Object) setXattr(metadata fs.Metadata) (err error) {
+ return nil
+}
diff --git a/docs/content/local.md b/docs/content/local.md
index a5004108fd4ba..c17a22c1a8a70 100644
--- a/docs/content/local.md
+++ b/docs/content/local.md
@@ -563,6 +563,32 @@ Properties:
- Type: MultiEncoder
- Default: Slash,Dot
+### Metadata
+
+Depending on which OS is in use the local backend may return only some
+of the system metadata. Setting system metadata is supported on all
+OSes but setting user metadata is only supported on linux, freebsd,
+netbsd, macOS and Solaris. It is **not** supported on Windows yet
+([see pkg/attrs#47](https://github.com/pkg/xattr/issues/47)).
+
+User metadata is stored as extended attributes (which may not be
+supported by all file systems) under the "user.*" prefix.
+
+Here are the possible system metadata items for the local backend.
+
+| Name | Help | Type | Example | Read Only |
+|------|------|------|---------|-----------|
+| atime | Time of last access | RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 | N |
+| btime | Time of file birth (creation) | RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 | N |
+| gid | Group ID of owner | decimal number | 500 | N |
+| mode | File type and mode | octal, unix style | 0100664 | N |
+| mtime | Time of last modification | RFC 3339 | 2006-01-02T15:04:05.999999999Z07:00 | N |
+| rdev | Device ID (if special file) | hexadecimal | 1abc | N |
+| uid | User ID of owner | decimal number | 500 | N |
+
+
+See the [metadata](/docs/#metadata) docs for more info.
+
## Backend commands
Here are the commands specific to the local backend.
diff --git a/go.mod b/go.mod
index 1fe102fd68853..e61cdce97dbc6 100644
--- a/go.mod
+++ b/go.mod
@@ -76,6 +76,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/jlaffaye/ftp v0.0.0-20220524001917-dfa1e758f3af
+ github.com/pkg/xattr v0.4.7 // indirect
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
)
diff --git a/go.sum b/go.sum
index e957043f89908..9cc774dd30d10 100644
--- a/go.sum
+++ b/go.sum
@@ -478,6 +478,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.5-0.20211228200725-31aac3e1878d h1:7cHNeARnMq3icpbMdvyUELykWM4zOj5NRhH2Y3sfgBc=
github.com/pkg/sftp v1.13.5-0.20211228200725-31aac3e1878d/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
+github.com/pkg/xattr v0.4.7 h1:XoA3KzmFvyPlH4RwX5eMcgtzcaGBaSvgt3IoFQfbrmQ=
+github.com/pkg/xattr v0.4.7/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
@@ -882,6 +884,7 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
From 866c873daa97f90f0bf6d303a1ea92d248f17b1d Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 11:51:46 +0100
Subject: [PATCH 125/560] backend: allow wrapping backend tests to run in make
quicktest
---
backend/chunker/chunker_test.go | 1 +
backend/combine/combine_test.go | 2 ++
backend/compress/compress_test.go | 1 +
backend/crypt/crypt_test.go | 13 +++++++++++++
backend/hasher/hasher_test.go | 1 +
backend/local/local_test.go | 5 +++--
backend/memory/memory_test.go | 5 +++--
backend/union/union_test.go | 6 ++++++
fstest/fstests/fstests.go | 3 ++-
9 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/backend/chunker/chunker_test.go b/backend/chunker/chunker_test.go
index 4acdf5b5aab18..cca76f9e72776 100644
--- a/backend/chunker/chunker_test.go
+++ b/backend/chunker/chunker_test.go
@@ -53,6 +53,7 @@ func TestIntegration(t *testing.T) {
{Name: name, Key: "type", Value: "chunker"},
{Name: name, Key: "remote", Value: tempDir},
}
+ opt.QuickTestOK = true
}
fstests.Run(t, &opt)
}
diff --git a/backend/combine/combine_test.go b/backend/combine/combine_test.go
index c6274c562097a..46b49bcf0305f 100644
--- a/backend/combine/combine_test.go
+++ b/backend/combine/combine_test.go
@@ -35,6 +35,7 @@ func TestLocal(t *testing.T) {
{Name: name, Key: "type", Value: "combine"},
{Name: name, Key: "upstreams", Value: upstreams},
},
+ QuickTestOK: true,
})
}
@@ -50,6 +51,7 @@ func TestMemory(t *testing.T) {
{Name: name, Key: "type", Value: "combine"},
{Name: name, Key: "upstreams", Value: upstreams},
},
+ QuickTestOK: true,
})
}
diff --git a/backend/compress/compress_test.go b/backend/compress/compress_test.go
index 76baf22aa91cd..356e9dd438295 100644
--- a/backend/compress/compress_test.go
+++ b/backend/compress/compress_test.go
@@ -61,5 +61,6 @@ func TestRemoteGzip(t *testing.T) {
{Name: name, Key: "remote", Value: tempdir},
{Name: name, Key: "compression_mode", Value: "gzip"},
},
+ QuickTestOK: true,
})
}
diff --git a/backend/crypt/crypt_test.go b/backend/crypt/crypt_test.go
index 8369a60f7d2dd..d4b9dc1e43283 100644
--- a/backend/crypt/crypt_test.go
+++ b/backend/crypt/crypt_test.go
@@ -4,6 +4,7 @@ package crypt_test
import (
"os"
"path/filepath"
+ "runtime"
"testing"
"github.com/rclone/rclone/backend/crypt"
@@ -46,6 +47,7 @@ func TestStandardBase32(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -67,6 +69,7 @@ func TestStandardBase64(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -88,6 +91,7 @@ func TestStandardBase32768(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -109,6 +113,7 @@ func TestOff(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -117,6 +122,9 @@ func TestObfuscate(t *testing.T) {
if *fstest.RemoteName != "" {
t.Skip("Skipping as -remote set")
}
+ if runtime.GOOS == "darwin" {
+ t.Skip("Skipping on macOS as obfuscating control characters makes filenames macOS can't cope with")
+ }
tempdir := filepath.Join(os.TempDir(), "rclone-crypt-test-obfuscate")
name := "TestCrypt3"
fstests.Run(t, &fstests.Opt{
@@ -131,6 +139,7 @@ func TestObfuscate(t *testing.T) {
SkipBadWindowsCharacters: true,
UnimplementableFsMethods: []string{"OpenWriterAt"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -139,6 +148,9 @@ func TestNoDataObfuscate(t *testing.T) {
if *fstest.RemoteName != "" {
t.Skip("Skipping as -remote set")
}
+ if runtime.GOOS == "darwin" {
+ t.Skip("Skipping on macOS as obfuscating control characters makes filenames macOS can't cope with")
+ }
tempdir := filepath.Join(os.TempDir(), "rclone-crypt-test-obfuscate")
name := "TestCrypt4"
fstests.Run(t, &fstests.Opt{
@@ -154,5 +166,6 @@ func TestNoDataObfuscate(t *testing.T) {
SkipBadWindowsCharacters: true,
UnimplementableFsMethods: []string{"OpenWriterAt"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
diff --git a/backend/hasher/hasher_test.go b/backend/hasher/hasher_test.go
index 09d60f33d71c4..23feb9b96ca7f 100644
--- a/backend/hasher/hasher_test.go
+++ b/backend/hasher/hasher_test.go
@@ -33,6 +33,7 @@ func TestIntegration(t *testing.T) {
{Name: "TestHasher", Key: "remote", Value: tempDir},
}
opt.RemoteName = "TestHasher:"
+ opt.QuickTestOK = true
}
fstests.Run(t, &opt)
}
diff --git a/backend/local/local_test.go b/backend/local/local_test.go
index 8ecab2dbdc3d0..d0d54ec26c4c1 100644
--- a/backend/local/local_test.go
+++ b/backend/local/local_test.go
@@ -11,7 +11,8 @@ import (
// TestIntegration runs integration tests against the remote
func TestIntegration(t *testing.T) {
fstests.Run(t, &fstests.Opt{
- RemoteName: "",
- NilObject: (*local.Object)(nil),
+ RemoteName: "",
+ NilObject: (*local.Object)(nil),
+ QuickTestOK: true,
})
}
diff --git a/backend/memory/memory_test.go b/backend/memory/memory_test.go
index 3d768fec0efbe..9989bc60de2df 100644
--- a/backend/memory/memory_test.go
+++ b/backend/memory/memory_test.go
@@ -10,7 +10,8 @@ import (
// TestIntegration runs integration tests against the remote
func TestIntegration(t *testing.T) {
fstests.Run(t, &fstests.Opt{
- RemoteName: ":memory:",
- NilObject: (*Object)(nil),
+ RemoteName: ":memory:",
+ NilObject: (*Object)(nil),
+ QuickTestOK: true,
})
}
diff --git a/backend/union/union_test.go b/backend/union/union_test.go
index 8b69c4c27f8b3..f5a9948f61d4f 100644
--- a/backend/union/union_test.go
+++ b/backend/union/union_test.go
@@ -41,6 +41,7 @@ func TestStandard(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -62,6 +63,7 @@ func TestRO(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -83,6 +85,7 @@ func TestNC(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -104,6 +107,7 @@ func TestPolicy1(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -125,6 +129,7 @@ func TestPolicy2(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
@@ -146,5 +151,6 @@ func TestPolicy3(t *testing.T) {
},
UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"},
UnimplementableObjectMethods: []string{"MimeType"},
+ QuickTestOK: true,
})
}
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 9cc95896eac41..7a72ef5f518d2 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -327,6 +327,7 @@ type Opt struct {
SkipFsCheckWrap bool // if set skip FsCheckWrap
SkipObjectCheckWrap bool // if set skip ObjectCheckWrap
SkipInvalidUTF8 bool // if set skip invalid UTF-8 checks
+ QuickTestOK bool // if set, run this test with make quicktest
}
// returns true if x is found in ss
@@ -392,7 +393,7 @@ func Run(t *testing.T, opt *Opt) {
unwrappableFsMethods = []string{"Command"} // these Fs methods don't need to be wrapped ever
)
- if strings.HasSuffix(os.Getenv("RCLONE_CONFIG"), "/notfound") && *fstest.RemoteName == "" {
+ if strings.HasSuffix(os.Getenv("RCLONE_CONFIG"), "/notfound") && *fstest.RemoteName == "" && !opt.QuickTestOK {
t.Skip("quicktest only")
}
From cd1735bb10bbacec4d8f8a0e8c6d92e427098463 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 14:27:25 +0100
Subject: [PATCH 126/560] cache: mark as not supporting metadata
---
backend/cache/cache_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/cache/cache_test.go b/backend/cache/cache_test.go
index 44307ea6d58b3..9ad4e31e5afe8 100644
--- a/backend/cache/cache_test.go
+++ b/backend/cache/cache_test.go
@@ -19,7 +19,7 @@ func TestIntegration(t *testing.T) {
RemoteName: "TestCache:",
NilObject: (*cache.Object)(nil),
UnimplementableFsMethods: []string{"PublicLink", "OpenWriterAt"},
- UnimplementableObjectMethods: []string{"MimeType", "ID", "GetTier", "SetTier"},
+ UnimplementableObjectMethods: []string{"MimeType", "ID", "GetTier", "SetTier", "Metadata"},
SkipInvalidUTF8: true, // invalid UTF-8 confuses the cache
})
}
From ba5760ff3833a064dcabb498553a8616a4d24a0e Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 14:27:43 +0100
Subject: [PATCH 127/560] chunker: mark as not supporting metadata
---
backend/chunker/chunker_test.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/backend/chunker/chunker_test.go b/backend/chunker/chunker_test.go
index cca76f9e72776..7f0c0310d0856 100644
--- a/backend/chunker/chunker_test.go
+++ b/backend/chunker/chunker_test.go
@@ -35,6 +35,7 @@ func TestIntegration(t *testing.T) {
"MimeType",
"GetTier",
"SetTier",
+ "Metadata",
},
UnimplementableFsMethods: []string{
"PublicLink",
From 8c483daf85d6a3644b87772bbedfa06e51842b95 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 14:27:58 +0100
Subject: [PATCH 128/560] combine: support metadata
---
backend/combine/combine.go | 46 ++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/backend/combine/combine.go b/backend/combine/combine.go
index 934ed76f44ada..36fdc69c6ea56 100644
--- a/backend/combine/combine.go
+++ b/backend/combine/combine.go
@@ -31,6 +31,9 @@ func init() {
Name: "combine",
Description: "Combine several remotes into one",
NewFs: NewFs,
+ MetadataInfo: &fs.MetadataInfo{
+ Help: `Any metadata supported by the underlying remote is read and written.`,
+ },
Options: []fs.Option{{
Name: "upstreams",
Help: `Upstreams for combining
@@ -222,6 +225,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (outFs fs
BucketBased: true,
SetTier: true,
GetTier: true,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: true,
}).Fill(ctx, f)
canMove := true
for _, u := range f.upstreams {
@@ -925,6 +931,45 @@ func (o *Object) UnWrap() fs.Object {
return o.Object
}
+// GetTier returns storage tier or class of the Object
+func (o *Object) GetTier() string {
+ do, ok := o.Object.(fs.GetTierer)
+ if !ok {
+ return ""
+ }
+ return do.GetTier()
+}
+
+// ID returns the ID of the Object if known, or "" if not
+func (o *Object) ID() string {
+ do, ok := o.Object.(fs.IDer)
+ if !ok {
+ return ""
+ }
+ return do.ID()
+}
+
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
+ do, ok := o.Object.(fs.Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
+
+// SetTier performs changing storage tier of the Object if
+// multiple storage classes supported
+func (o *Object) SetTier(tier string) error {
+ do, ok := o.Object.(fs.SetTierer)
+ if !ok {
+ return errors.New("underlying remote does not support SetTier")
+ }
+ return do.SetTier(tier)
+}
+
// Check the interfaces are satisfied
var (
_ fs.Fs = (*Fs)(nil)
@@ -938,4 +983,5 @@ var (
_ fs.Abouter = (*Fs)(nil)
_ fs.ListRer = (*Fs)(nil)
_ fs.Shutdowner = (*Fs)(nil)
+ _ fs.FullObject = (*Object)(nil)
)
From c198700812030012e21d18886ec0589daf1392ad Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 14:28:28 +0100
Subject: [PATCH 129/560] compress: support metadata
---
backend/compress/compress.go | 75 ++++++++++++++++++++++++++++++++----
1 file changed, 68 insertions(+), 7 deletions(-)
diff --git a/backend/compress/compress.go b/backend/compress/compress.go
index dcc54a11c0e9d..9033b38ff4f11 100644
--- a/backend/compress/compress.go
+++ b/backend/compress/compress.go
@@ -70,6 +70,9 @@ func init() {
Name: "compress",
Description: "Compress a remote",
NewFs: NewFs,
+ MetadataInfo: &fs.MetadataInfo{
+ Help: `Any metadata supported by the underlying remote is read and written.`,
+ },
Options: []fs.Option{{
Name: "remote",
Help: "Remote to compress.",
@@ -180,6 +183,9 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
SetTier: true,
BucketBased: true,
CanHaveEmptyDirectories: true,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: true,
}).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs)
// We support reading MIME types no matter the wrapped fs
f.features.ReadMimeType = true
@@ -1214,6 +1220,21 @@ func (o *Object) MimeType(ctx context.Context) string {
return o.meta.MimeType
}
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
+ err := o.loadMetadataIfNotLoaded(ctx)
+ if err != nil {
+ return nil, err
+ }
+ do, ok := o.mo.(fs.Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
+
// Hash returns the selected checksum of the file
// If no checksum is available it returns ""
func (o *Object) Hash(ctx context.Context, ht hash.Type) (string, error) {
@@ -1360,6 +1381,51 @@ func (o *ObjectInfo) Hash(ctx context.Context, ht hash.Type) (string, error) {
return "", nil // cannot know the checksum
}
+// ID returns the ID of the Object if known, or "" if not
+func (o *ObjectInfo) ID() string {
+ do, ok := o.src.(fs.IDer)
+ if !ok {
+ return ""
+ }
+ return do.ID()
+}
+
+// MimeType returns the content type of the Object if
+// known, or "" if not
+func (o *ObjectInfo) MimeType(ctx context.Context) string {
+ do, ok := o.src.(fs.MimeTyper)
+ if !ok {
+ return ""
+ }
+ return do.MimeType(ctx)
+}
+
+// UnWrap returns the Object that this Object is wrapping or
+// nil if it isn't wrapping anything
+func (o *ObjectInfo) UnWrap() fs.Object {
+ return fs.UnWrapObjectInfo(o.src)
+}
+
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *ObjectInfo) Metadata(ctx context.Context) (fs.Metadata, error) {
+ do, ok := o.src.(fs.Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
+
+// GetTier returns storage tier or class of the Object
+func (o *ObjectInfo) GetTier() string {
+ do, ok := o.src.(fs.GetTierer)
+ if !ok {
+ return ""
+ }
+ return do.GetTier()
+}
+
// ID returns the ID of the Object if known, or "" if not
func (o *Object) ID() string {
do, ok := o.Object.(fs.IDer)
@@ -1412,11 +1478,6 @@ var (
_ fs.ChangeNotifier = (*Fs)(nil)
_ fs.PublicLinker = (*Fs)(nil)
_ fs.Shutdowner = (*Fs)(nil)
- _ fs.ObjectInfo = (*ObjectInfo)(nil)
- _ fs.GetTierer = (*Object)(nil)
- _ fs.SetTierer = (*Object)(nil)
- _ fs.Object = (*Object)(nil)
- _ fs.ObjectUnWrapper = (*Object)(nil)
- _ fs.IDer = (*Object)(nil)
- _ fs.MimeTyper = (*Object)(nil)
+ _ fs.FullObjectInfo = (*ObjectInfo)(nil)
+ _ fs.FullObject = (*Object)(nil)
)
From bf4a16ae308df7c6e3ad1305e60343d75384ace3 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 14:29:00 +0100
Subject: [PATCH 130/560] crypt: support metadata
---
backend/crypt/crypt.go | 78 +++++++++++++++++++++++++---
backend/crypt/crypt_internal_test.go | 4 +-
2 files changed, 75 insertions(+), 7 deletions(-)
diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go
index 33e57cdb9b513..6f599cafb2c90 100644
--- a/backend/crypt/crypt.go
+++ b/backend/crypt/crypt.go
@@ -28,6 +28,9 @@ func init() {
Description: "Encrypt/Decrypt a remote",
NewFs: NewFs,
CommandHelp: commandHelp,
+ MetadataInfo: &fs.MetadataInfo{
+ Help: `Any metadata supported by the underlying remote is read and written.`,
+ },
Options: []fs.Option{{
Name: "remote",
Help: "Remote to encrypt/decrypt.\n\nNormally should contain a ':' and a path, e.g. \"myremote:path/to/dir\",\n\"myremote:bucket\" or maybe \"myremote:\" (not recommended).",
@@ -241,6 +244,9 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
SetTier: true,
GetTier: true,
ServerSideAcrossConfigs: opt.ServerSideAcrossConfigs,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: true,
}).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs)
return f, err
@@ -1056,6 +1062,50 @@ func (o *ObjectInfo) Hash(ctx context.Context, hash hash.Type) (string, error) {
return "", nil
}
+// GetTier returns storage tier or class of the Object
+func (o *ObjectInfo) GetTier() string {
+ do, ok := o.ObjectInfo.(fs.GetTierer)
+ if !ok {
+ return ""
+ }
+ return do.GetTier()
+}
+
+// ID returns the ID of the Object if known, or "" if not
+func (o *ObjectInfo) ID() string {
+ do, ok := o.ObjectInfo.(fs.IDer)
+ if !ok {
+ return ""
+ }
+ return do.ID()
+}
+
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *ObjectInfo) Metadata(ctx context.Context) (fs.Metadata, error) {
+ do, ok := o.ObjectInfo.(fs.Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
+
+// MimeType returns the content type of the Object if
+// known, or "" if not
+//
+// This is deliberately unsupported so we don't leak mime type info by
+// default.
+func (o *ObjectInfo) MimeType(ctx context.Context) string {
+ return ""
+}
+
+// UnWrap returns the Object that this Object is wrapping or
+// nil if it isn't wrapping anything
+func (o *ObjectInfo) UnWrap() fs.Object {
+ return fs.UnWrapObjectInfo(o.ObjectInfo)
+}
+
// ID returns the ID of the Object if known, or "" if not
func (o *Object) ID() string {
do, ok := o.Object.(fs.IDer)
@@ -1084,6 +1134,26 @@ func (o *Object) GetTier() string {
return do.GetTier()
}
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
+ do, ok := o.Object.(fs.Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
+
+// MimeType returns the content type of the Object if
+// known, or "" if not
+//
+// This is deliberately unsupported so we don't leak mime type info by
+// default.
+func (o *Object) MimeType(ctx context.Context) string {
+ return ""
+}
+
// Check the interfaces are satisfied
var (
_ fs.Fs = (*Fs)(nil)
@@ -1106,10 +1176,6 @@ var (
_ fs.UserInfoer = (*Fs)(nil)
_ fs.Disconnecter = (*Fs)(nil)
_ fs.Shutdowner = (*Fs)(nil)
- _ fs.ObjectInfo = (*ObjectInfo)(nil)
- _ fs.Object = (*Object)(nil)
- _ fs.ObjectUnWrapper = (*Object)(nil)
- _ fs.IDer = (*Object)(nil)
- _ fs.SetTierer = (*Object)(nil)
- _ fs.GetTierer = (*Object)(nil)
+ _ fs.FullObjectInfo = (*ObjectInfo)(nil)
+ _ fs.FullObject = (*Object)(nil)
)
diff --git a/backend/crypt/crypt_internal_test.go b/backend/crypt/crypt_internal_test.go
index 2f7f1f8b36d0f..82587763061d7 100644
--- a/backend/crypt/crypt_internal_test.go
+++ b/backend/crypt/crypt_internal_test.go
@@ -91,7 +91,9 @@ func testObjectInfo(t *testing.T, f *Fs, wrap bool) {
src := f.newObjectInfo(oi, nonce)
// Test ObjectInfo methods
- assert.Equal(t, int64(outBuf.Len()), src.Size())
+ if !f.opt.NoDataEncryption {
+ assert.Equal(t, int64(outBuf.Len()), src.Size())
+ }
assert.Equal(t, f, src.Fs())
assert.NotEqual(t, path, src.Remote())
From ed87ae51c0fa42f9523a9df4d87d452452d1fd84 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 14:29:15 +0100
Subject: [PATCH 131/560] union: support metadata
---
backend/union/entry.go | 47 +++++++++++++++++++++++++--
backend/union/union.go | 8 ++++-
backend/union/upstream/upstream.go | 52 ++++++++++++++++++++++++++++++
3 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/backend/union/entry.go b/backend/union/entry.go
index efd2cbaef71e8..023f3bb456d7c 100644
--- a/backend/union/entry.go
+++ b/backend/union/entry.go
@@ -2,6 +2,7 @@ package union
import (
"context"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -34,9 +35,8 @@ type entry interface {
candidates() []upstream.Entry
}
-// UnWrap returns the Object that this Object is wrapping or
-// nil if it isn't wrapping anything
-func (o *Object) UnWrap() *upstream.Object {
+// UnWrapUpstream returns the upstream Object that this Object is wrapping
+func (o *Object) UnWrapUpstream() *upstream.Object {
return o.Object
}
@@ -140,6 +140,42 @@ func (o *Object) SetModTime(ctx context.Context, t time.Time) error {
return errs.Err()
}
+// GetTier returns storage tier or class of the Object
+func (o *Object) GetTier() string {
+ do, ok := o.Object.Object.(fs.GetTierer)
+ if !ok {
+ return ""
+ }
+ return do.GetTier()
+}
+
+// ID returns the ID of the Object if known, or "" if not
+func (o *Object) ID() string {
+ do, ok := o.Object.Object.(fs.IDer)
+ if !ok {
+ return ""
+ }
+ return do.ID()
+}
+
+// MimeType returns the content type of the Object if known
+func (o *Object) MimeType(ctx context.Context) (mimeType string) {
+ if do, ok := o.Object.Object.(fs.MimeTyper); ok {
+ mimeType = do.MimeType(ctx)
+ }
+ return mimeType
+}
+
+// SetTier performs changing storage tier of the Object if
+// multiple storage classes supported
+func (o *Object) SetTier(tier string) error {
+ do, ok := o.Object.Object.(fs.SetTierer)
+ if !ok {
+ return errors.New("underlying remote does not support SetTier")
+ }
+ return do.SetTier(tier)
+}
+
// ModTime returns the modification date of the directory
// It returns the latest ModTime of all candidates
func (d *Directory) ModTime(ctx context.Context) (t time.Time) {
@@ -164,3 +200,8 @@ func (d *Directory) Size() (s int64) {
}
return s
}
+
+// Check the interfaces are satisfied
+var (
+ _ fs.FullObject = (*Object)(nil)
+)
diff --git a/backend/union/union.go b/backend/union/union.go
index 9776c55d2a54f..e1ec53e85eacc 100644
--- a/backend/union/union.go
+++ b/backend/union/union.go
@@ -30,6 +30,9 @@ func init() {
Name: "union",
Description: "Union merges the contents of several upstream fs",
NewFs: NewFs,
+ MetadataInfo: &fs.MetadataInfo{
+ Help: `Any metadata supported by the underlying remote is read and written.`,
+ },
Options: []fs.Option{{
Name: "upstreams",
Help: "List of space separated upstreams.\n\nCan be 'upstreama:test/dir upstreamb:', '\"upstreama:test/space:ro dir\" upstreamb:', etc.",
@@ -219,7 +222,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
fs.Debugf(src, "Can't copy - not same remote type")
return nil, fs.ErrorCantCopy
}
- o := srcObj.UnWrap()
+ o := srcObj.UnWrapUpstream()
su := o.UpstreamFs()
if su.Features().Copy == nil {
return nil, fs.ErrorCantCopy
@@ -881,6 +884,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
BucketBased: true,
SetTier: true,
GetTier: true,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: true,
}).Fill(ctx, f)
canMove := true
for _, f := range upstreams {
diff --git a/backend/union/upstream/upstream.go b/backend/union/upstream/upstream.go
index 1a85d783fe44b..c6e906107620b 100644
--- a/backend/union/upstream/upstream.go
+++ b/backend/union/upstream/upstream.go
@@ -245,6 +245,53 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
return nil
}
+// GetTier returns storage tier or class of the Object
+func (o *Object) GetTier() string {
+ do, ok := o.Object.(fs.GetTierer)
+ if !ok {
+ return ""
+ }
+ return do.GetTier()
+}
+
+// ID returns the ID of the Object if known, or "" if not
+func (o *Object) ID() string {
+ do, ok := o.Object.(fs.IDer)
+ if !ok {
+ return ""
+ }
+ return do.ID()
+}
+
+// MimeType returns the content type of the Object if known
+func (o *Object) MimeType(ctx context.Context) (mimeType string) {
+ if do, ok := o.Object.(fs.MimeTyper); ok {
+ mimeType = do.MimeType(ctx)
+ }
+ return mimeType
+}
+
+// SetTier performs changing storage tier of the Object if
+// multiple storage classes supported
+func (o *Object) SetTier(tier string) error {
+ do, ok := o.Object.(fs.SetTierer)
+ if !ok {
+ return errors.New("underlying remote does not support SetTier")
+ }
+ return do.SetTier(tier)
+}
+
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
+ do, ok := o.Object.(fs.Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
+
// About gets quota information from the Fs
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
if atomic.LoadInt64(&f.cacheExpiry) <= time.Now().Unix() {
@@ -363,3 +410,8 @@ func (f *Fs) updateUsageCore(lock bool) error {
f.usage = usage
return nil
}
+
+// Check the interfaces are satisfied
+var (
+ _ fs.FullObject = (*Object)(nil)
+)
From 7e7a8a95e974ba40ed5154a93e268283d488e3b8 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Wed, 29 Jun 2022 14:52:16 +0100
Subject: [PATCH 132/560] hasher: support metadata
---
backend/hasher/hasher.go | 26 ++++++++++++++++++++------
backend/hasher/hasher_internal_test.go | 2 +-
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/backend/hasher/hasher.go b/backend/hasher/hasher.go
index 1870983e043d8..e80daedee6a35 100644
--- a/backend/hasher/hasher.go
+++ b/backend/hasher/hasher.go
@@ -27,6 +27,9 @@ func init() {
Name: "hasher",
Description: "Better checksums for other remotes",
NewFs: NewFs,
+ MetadataInfo: &fs.MetadataInfo{
+ Help: `Any metadata supported by the underlying remote is read and written.`,
+ },
CommandHelp: commandHelp,
Options: []fs.Option{{
Name: "remote",
@@ -158,6 +161,11 @@ func NewFs(ctx context.Context, fsname, rpath string, cmap configmap.Mapper) (fs
IsLocal: true,
ReadMimeType: true,
WriteMimeType: true,
+ SetTier: true,
+ GetTier: true,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: true,
}
f.features = stubFeatures.Fill(ctx, f).Mask(ctx, f.Fs).WrapsFs(f, f.Fs)
@@ -485,6 +493,17 @@ func (o *Object) MimeType(ctx context.Context) string {
return ""
}
+// Metadata returns metadata for an object
+//
+// It should return nil if there is no Metadata
+func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
+ do, ok := o.Object.(fs.Metadataer)
+ if !ok {
+ return nil, nil
+ }
+ return do.Metadata(ctx)
+}
+
// Check the interfaces are satisfied
var (
_ fs.Fs = (*Fs)(nil)
@@ -507,10 +526,5 @@ var (
_ fs.UserInfoer = (*Fs)(nil)
_ fs.Disconnecter = (*Fs)(nil)
_ fs.Shutdowner = (*Fs)(nil)
- _ fs.Object = (*Object)(nil)
- _ fs.ObjectUnWrapper = (*Object)(nil)
- _ fs.IDer = (*Object)(nil)
- _ fs.SetTierer = (*Object)(nil)
- _ fs.GetTierer = (*Object)(nil)
- _ fs.MimeTyper = (*Object)(nil)
+ _ fs.FullObject = (*Object)(nil)
)
diff --git a/backend/hasher/hasher_internal_test.go b/backend/hasher/hasher_internal_test.go
index 5332318344882..0ca418e20a965 100644
--- a/backend/hasher/hasher_internal_test.go
+++ b/backend/hasher/hasher_internal_test.go
@@ -35,7 +35,7 @@ func (f *Fs) testUploadFromCrypt(t *testing.T) {
// make a temporary crypt remote
ctx := context.Background()
pass := obscure.MustObscure("crypt")
- remote := fmt.Sprintf(":crypt,remote=%s,password=%s:", tempRoot, pass)
+ remote := fmt.Sprintf(`:crypt,remote="%s",password="%s":`, tempRoot, pass)
cryptFs, err := fs.NewFs(ctx, remote)
require.NoError(t, err)
From 73e3bb09d71c31814e00ebcad2eec9be3b74ccf0 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 23:13:12 +0200
Subject: [PATCH 133/560] http: fix missing response when using custom auth
handler
---
lib/http/auth/basic.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/http/auth/basic.go b/lib/http/auth/basic.go
index b2a2c658b45a3..69907c009371e 100644
--- a/lib/http/auth/basic.go
+++ b/lib/http/auth/basic.go
@@ -113,6 +113,7 @@ func CustomAuth(fn CustomAuthFn, realm string) httplib.Middleware {
if value != nil {
r = r.WithContext(context.WithValue(r.Context(), ContextAuthKey, value))
}
+ next.ServeHTTP(w, r)
}
})
}
From 9dbed02329456c71d2052a6365041deda94bc020 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Tue, 21 Jun 2022 01:14:06 +0200
Subject: [PATCH 134/560] jottacloud: always store username in config and use
it to avoid initial api request
Existing version did save username in config, but only when entering the custom
device/mountpoint sequence in config. Regardless of that, it did always look up the
username at startup with an api request.
This commit improves it so that the username will always be stored in config,
and when using standard authentication it picks it from the login token instead of
requesting it from the remote api, and also in fs constructor it picks it from config
instead of requesting it from remote api (again).
---
backend/jottacloud/jottacloud.go | 53 ++++++++++++++++++++------------
1 file changed, 34 insertions(+), 19 deletions(-)
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index 735b5181fb080..5998a04a2780e 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -152,7 +152,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
m.Set(configClientSecret, "")
srv := rest.NewClient(fshttp.NewClient(ctx))
- token, tokenEndpoint, err := doTokenAuth(ctx, srv, loginToken)
+ token, tokenEndpoint, username, err := doTokenAuth(ctx, srv, loginToken)
if err != nil {
return nil, fmt.Errorf("failed to get oauth token: %w", err)
}
@@ -161,6 +161,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
if err != nil {
return nil, fmt.Errorf("error while saving token: %w", err)
}
+ m.Set(configUsername, username)
return fs.ConfigGoto("choose_device")
case "legacy": // configure a jottacloud backend using legacy authentication
m.Set("configVersion", fmt.Sprint(legacyConfigVersion))
@@ -271,22 +272,30 @@ sync or the backup section, for example, you must choose yes.`)
if config.Result != "true" {
m.Set(configDevice, "")
m.Set(configMountpoint, "")
+ }
+ username, userOk := m.Get(configUsername)
+ if userOk && config.Result != "true" {
return fs.ConfigGoto("end")
}
oAuthClient, _, err := getOAuthClient(ctx, name, m)
if err != nil {
return nil, err
}
- jfsSrv := rest.NewClient(oAuthClient).SetRoot(jfsURL)
- apiSrv := rest.NewClient(oAuthClient).SetRoot(apiURL)
-
- cust, err := getCustomerInfo(ctx, apiSrv)
- if err != nil {
- return nil, err
+ if !userOk {
+ apiSrv := rest.NewClient(oAuthClient).SetRoot(apiURL)
+ cust, err := getCustomerInfo(ctx, apiSrv)
+ if err != nil {
+ return nil, err
+ }
+ username = cust.Username
+ m.Set(configUsername, username)
+ if config.Result != "true" {
+ return fs.ConfigGoto("end")
+ }
}
- m.Set(configUsername, cust.Username)
- acc, err := getDriveInfo(ctx, jfsSrv, cust.Username)
+ jfsSrv := rest.NewClient(oAuthClient).SetRoot(jfsURL)
+ acc, err := getDriveInfo(ctx, jfsSrv, username)
if err != nil {
return nil, err
}
@@ -582,10 +591,10 @@ func doLegacyAuth(ctx context.Context, srv *rest.Client, oauthConfig *oauth2.Con
}
// doTokenAuth runs the actual token request for V2 authentication
-func doTokenAuth(ctx context.Context, apiSrv *rest.Client, loginTokenBase64 string) (token oauth2.Token, tokenEndpoint string, err error) {
+func doTokenAuth(ctx context.Context, apiSrv *rest.Client, loginTokenBase64 string) (token oauth2.Token, tokenEndpoint string, username string, err error) {
loginTokenBytes, err := base64.RawURLEncoding.DecodeString(loginTokenBase64)
if err != nil {
- return token, "", err
+ return token, "", "", err
}
// decode login token
@@ -593,7 +602,7 @@ func doTokenAuth(ctx context.Context, apiSrv *rest.Client, loginTokenBase64 stri
decoder := json.NewDecoder(bytes.NewReader(loginTokenBytes))
err = decoder.Decode(&loginToken)
if err != nil {
- return token, "", err
+ return token, "", "", err
}
// retrieve endpoint urls
@@ -604,7 +613,7 @@ func doTokenAuth(ctx context.Context, apiSrv *rest.Client, loginTokenBase64 stri
var wellKnown api.WellKnown
_, err = apiSrv.CallJSON(ctx, &opts, nil, &wellKnown)
if err != nil {
- return token, "", err
+ return token, "", "", err
}
// prepare out token request with username and password
@@ -626,14 +635,14 @@ func doTokenAuth(ctx context.Context, apiSrv *rest.Client, loginTokenBase64 stri
var jsonToken api.TokenJSON
_, err = apiSrv.CallJSON(ctx, &opts, nil, &jsonToken)
if err != nil {
- return token, "", err
+ return token, "", "", err
}
token.AccessToken = jsonToken.AccessToken
token.RefreshToken = jsonToken.RefreshToken
token.TokenType = jsonToken.TokenType
token.Expiry = time.Now().Add(time.Duration(jsonToken.ExpiresIn) * time.Second)
- return token, wellKnown.TokenEndpoint, err
+ return token, wellKnown.TokenEndpoint, loginToken.Username, err
}
// getCustomerInfo queries general information about the account
@@ -935,11 +944,17 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
return err
})
- cust, err := getCustomerInfo(ctx, f.apiSrv)
- if err != nil {
- return nil, err
+ user, userOk := m.Get(configUsername)
+ if userOk {
+ f.user = user
+ } else {
+ fs.Infof(nil, "Username not found in config and must be looked up, reconfigure to avoid the extra request")
+ cust, err := getCustomerInfo(ctx, f.apiSrv)
+ if err != nil {
+ return nil, err
+ }
+ f.user = cust.Username
}
- f.user = cust.Username
f.setEndpoints()
if root != "" && !rootIsDir {
From accf91742c662f9bfe4202537b4235ac76f6c20d Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 4 Jul 2022 09:25:10 +0100
Subject: [PATCH 135/560] fs: add Type and FindFromFs to manage Fs and RegInfo
---
fs/newfs.go | 6 +++++-
fs/registry.go | 31 +++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/fs/newfs.go b/fs/newfs.go
index 1ac5e48071280..f64cb2c8dfa75 100644
--- a/fs/newfs.go
+++ b/fs/newfs.go
@@ -48,7 +48,11 @@ func NewFs(ctx context.Context, path string) (Fs, error) {
// These need to work as filesystem names as the VFS cache will use them
configName += suffix
}
- return fsInfo.NewFs(ctx, configName, fsPath, config)
+ f, err := fsInfo.NewFs(ctx, configName, fsPath, config)
+ if f != nil && (err == nil || err == ErrorIsFile) {
+ addReverse(f, fsInfo)
+ }
+ return f, err
}
// ConfigFs makes the config for calling NewFs with.
diff --git a/fs/registry.go b/fs/registry.go
index d380c7e0cb658..ca2c3d976f05c 100644
--- a/fs/registry.go
+++ b/fs/registry.go
@@ -10,6 +10,7 @@ import (
"reflect"
"sort"
"strings"
+ "sync"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct"
@@ -301,3 +302,33 @@ func MustFind(name string) *RegInfo {
}
return fs
}
+
+// Type returns a textual string to identify the type of the remote
+func Type(f Fs) string {
+ typeName := fmt.Sprintf("%T", f)
+ typeName = strings.TrimPrefix(typeName, "*")
+ typeName = strings.TrimSuffix(typeName, ".Fs")
+ return typeName
+}
+
+var (
+ typeToRegInfoMu sync.Mutex
+ typeToRegInfo = map[string]*RegInfo{}
+)
+
+// Add the RegInfo to the reverse map
+func addReverse(f Fs, fsInfo *RegInfo) {
+ typeToRegInfoMu.Lock()
+ defer typeToRegInfoMu.Unlock()
+ typeToRegInfo[Type(f)] = fsInfo
+}
+
+// FindFromFs finds the *RegInfo used to create this Fs, provided
+// it was created by fs.NewFs or cache.Get
+//
+// It returns nil if not found
+func FindFromFs(f Fs) *RegInfo {
+ typeToRegInfoMu.Lock()
+ defer typeToRegInfoMu.Unlock()
+ return typeToRegInfo[Type(f)]
+}
From a58b482061bce6dda4f65c811c499d1aefbbd481 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 4 Jul 2022 09:25:49 +0100
Subject: [PATCH 136/560] fstests: fix Metadata tests on remotes with
additional config
---
fstest/fstests/fstests.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 7a72ef5f518d2..286a6787102d2 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -1396,8 +1396,8 @@ func Run(t *testing.T, opt *Opt) {
obj := findObject(ctx, t, f, file1.Path)
do, objectHasMetadata := obj.(fs.Metadataer)
if objectHasMetadata || features.ReadMetadata || features.WriteMetadata || features.UserMetadata {
- fsInfo, _, _, _, err := fs.ParseRemote(fs.ConfigString(f))
- require.NoError(t, err)
+ fsInfo := fs.FindFromFs(f)
+ require.NotNil(t, fsInfo)
require.NotNil(t, fsInfo.MetadataInfo, "Object declares metadata support but no MetadataInfo in RegInfo")
}
if !objectHasMetadata {
From 06182a344340758d9d4f499ac5808d1193f9600a Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Mon, 4 Jul 2022 09:41:46 +0100
Subject: [PATCH 137/560] s3: actually compress the payload for content-type
gzip test
---
backend/s3/s3_internal_test.go | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/backend/s3/s3_internal_test.go b/backend/s3/s3_internal_test.go
index ecd39f41f3ec8..2568968d6700d 100644
--- a/backend/s3/s3_internal_test.go
+++ b/backend/s3/s3_internal_test.go
@@ -1,6 +1,8 @@
package s3
import (
+ "bytes"
+ "compress/gzip"
"context"
"fmt"
"testing"
@@ -14,9 +16,20 @@ import (
"github.com/stretchr/testify/require"
)
+func gz(t *testing.T, s string) string {
+ var buf bytes.Buffer
+ zw := gzip.NewWriter(&buf)
+ _, err := zw.Write([]byte(s))
+ require.NoError(t, err)
+ err = zw.Close()
+ require.NoError(t, err)
+ return buf.String()
+}
+
func (f *Fs) InternalTestMetadata(t *testing.T) {
ctx := context.Background()
- contents := random.String(100)
+ contents := gz(t, random.String(1000))
+
item := fstest.NewItem("test-metadata", contents, fstest.Time("2001-05-06T04:05:06.499999999Z"))
btime := time.Now()
metadata := fs.Metadata{
From 424a1f39eb376ccfc524b56f5dd835d1fa6a0405 Mon Sep 17 00:00:00 2001
From: Anthrazz <25553648+Anthrazz@users.noreply.github.com>
Date: Mon, 4 Jul 2022 08:54:37 +0200
Subject: [PATCH 138/560] sftp: add Hetzner Storage Boxes to supported sftp
backends
---
docs/content/_index.md | 1 +
docs/content/sftp.md | 9 ++++++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/content/_index.md b/docs/content/_index.md
index 58410d6d4330d..b6e2ac4d1c0ac 100644
--- a/docs/content/_index.md
+++ b/docs/content/_index.md
@@ -127,6 +127,7 @@ WebDAV or S3, that work out of the box.)
{{< provider name="Google Drive" home="https://www.google.com/drive/" config="/drive/" >}}
{{< provider name="Google Photos" home="https://www.google.com/photos/about/" config="/googlephotos/" >}}
{{< provider name="HDFS" home="https://hadoop.apache.org/" config="/hdfs/" >}}
+{{< provider name="Hetzner Storage Box" home="https://www.hetzner.com/storage/storage-box" config="/sftp/#hetzner-storage-box" >}}
{{< provider name="HTTP" home="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" config="/http/" >}}
{{< provider name="Hubic" home="https://hubic.com/" config="/hubic/" >}}
{{< provider name="Internet Archive" home="https://archive.org/" config="/internetarchive/" >}}
diff --git a/docs/content/sftp.md b/docs/content/sftp.md
index fa0f5ba054943..ed5eec9de3d68 100644
--- a/docs/content/sftp.md
+++ b/docs/content/sftp.md
@@ -11,6 +11,7 @@ Protocol](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol).
The SFTP backend can be used with a number of different providers:
{{< provider_list >}}
+{{< provider name="Hetzner Storage Box" home="https://www.hetzner.com/storage/storage-box" config="/sftp/#hetzner-storage-box">}}
{{< provider name="rsync.net" home="https://rsync.net/products/rclone.html" config="/sftp/#rsync-net">}}
{{< /provider_list >}}
@@ -25,7 +26,7 @@ would list the home directory of the user cofigured in the rclone remote config
directory for remote machine (i.e. `/`)
Note that some SFTP servers will need the leading / - Synology is a
-good example of this. rsync.net, on the other hand, requires users to
+good example of this. rsync.net and Hetzner, on the other hand, requires users to
OMIT the leading /.
Note that by default rclone will try to execute shell commands on
@@ -792,3 +793,9 @@ Note that `--timeout` and `--contimeout` are both supported.
rsync.net is supported through the SFTP backend.
See [rsync.net's documentation of rclone examples](https://www.rsync.net/products/rclone.html).
+
+## Hetzner Storage Box {#hetzner-storage-box}
+
+Hetzner Storage Boxes are supported through the SFTP backend on port 23.
+
+See [Hetzner's documentation for details](https://docs.hetzner.com/robot/storage-box/access/access-ssh-rsync-borg#rclone)
From 060c8dfff092f732aef79b9b46a46e32d07578ee Mon Sep 17 00:00:00 2001
From: zzr93 <34027824+zzr93@users.noreply.github.com>
Date: Mon, 4 Jul 2022 17:18:04 +0800
Subject: [PATCH 139/560] operations: use correct src/dst in some log messages
Most of the time this will make no difference to user logs, however
the difference may be visible in JSON logs and on the rare occasions
src and dst are pointing to different file names.
---
fs/operations/operations.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index 1ea41853fbe6d..e00a20a9e066f 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -89,7 +89,7 @@ func checkHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object, ht hash.
return dstErr
}
if dstHash == "" {
- fs.Debugf(src, "Dst hash empty - aborting Src hash check")
+ fs.Debugf(dst, "Dst hash empty - aborting Src hash check")
return errNoHash
}
return nil
@@ -100,11 +100,11 @@ func checkHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object, ht hash.
}
if srcErr != nil {
err = fs.CountError(srcErr)
- fs.Errorf(dst, "Failed to calculate src hash: %v", err)
+ fs.Errorf(src, "Failed to calculate src hash: %v", err)
}
if dstErr != nil {
err = fs.CountError(dstErr)
- fs.Errorf(src, "Failed to calculate dst hash: %v", err)
+ fs.Errorf(dst, "Failed to calculate dst hash: %v", err)
}
if err != nil {
return false, ht, srcHash, dstHash, err
From 0772cae3142dc153c056157dbba624ddecad2e02 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 16:01:19 +0200
Subject: [PATCH 140/560] staticcheck: use result of type assertion to simplify
cases
---
backend/union/union.go | 6 +++---
backend/union/upstream/upstream.go | 6 +++---
fs/operations/reopen.go | 6 +++---
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/backend/union/union.go b/backend/union/union.go
index e1ec53e85eacc..c93fc06497927 100644
--- a/backend/union/union.go
+++ b/backend/union/union.go
@@ -85,16 +85,16 @@ func (f *Fs) wrapEntries(entries ...upstream.Entry) (entry, error) {
if err != nil {
return nil, err
}
- switch e.(type) {
+ switch e := e.(type) {
case *upstream.Object:
return &Object{
- Object: e.(*upstream.Object),
+ Object: e,
fs: f,
co: entries,
}, nil
case *upstream.Directory:
return &Directory{
- Directory: e.(*upstream.Directory),
+ Directory: e,
cd: entries,
}, nil
default:
diff --git a/backend/union/upstream/upstream.go b/backend/union/upstream/upstream.go
index c6e906107620b..06f3b9014dc52 100644
--- a/backend/union/upstream/upstream.go
+++ b/backend/union/upstream/upstream.go
@@ -129,11 +129,11 @@ func (f *Fs) WrapObject(o fs.Object) *Object {
// WrapEntry wraps an fs.DirEntry to include the info
// of the upstream Fs
func (f *Fs) WrapEntry(e fs.DirEntry) (Entry, error) {
- switch e.(type) {
+ switch e := e.(type) {
case fs.Object:
- return f.WrapObject(e.(fs.Object)), nil
+ return f.WrapObject(e), nil
case fs.Directory:
- return f.WrapDirectory(e.(fs.Directory)), nil
+ return f.WrapDirectory(e), nil
default:
return nil, fmt.Errorf("unknown object type %T", e)
}
diff --git a/fs/operations/reopen.go b/fs/operations/reopen.go
index 1f575d7cc6c33..e0f38cfcb1bf6 100644
--- a/fs/operations/reopen.go
+++ b/fs/operations/reopen.go
@@ -59,11 +59,11 @@ func (h *ReOpen) open() error {
var hashOption *fs.HashesOption
var rangeOption *fs.RangeOption
for _, option := range h.options {
- switch option.(type) {
+ switch option := option.(type) {
case *fs.HashesOption:
- hashOption = option.(*fs.HashesOption)
+ hashOption = option
case *fs.RangeOption:
- rangeOption = option.(*fs.RangeOption)
+ rangeOption = option
case *fs.HTTPOption:
opts = append(opts, option)
default:
From 3435bf7f34ed0f7821d4119630496ad91427378f Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 15:52:30 +0200
Subject: [PATCH 141/560] staticcheck: no value of type int64 is greater than
math.MaxInt64
---
backend/union/upstream/upstream.go | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/backend/union/upstream/upstream.go b/backend/union/upstream/upstream.go
index 06f3b9014dc52..35b477f7930ab 100644
--- a/backend/union/upstream/upstream.go
+++ b/backend/union/upstream/upstream.go
@@ -320,11 +320,7 @@ func (f *Fs) GetFreeSpace() (int64, error) {
if f.usage.Free == nil {
return math.MaxInt64 - 1, ErrUsageFieldNotSupported
}
- free := *f.usage.Free
- if free >= math.MaxInt64 {
- free = math.MaxInt64 - 1
- }
- return free, nil
+ return *f.usage.Free, nil
}
// GetUsedSpace get the used space of the fs
@@ -342,11 +338,7 @@ func (f *Fs) GetUsedSpace() (int64, error) {
if f.usage.Used == nil {
return 0, ErrUsageFieldNotSupported
}
- used := *f.usage.Used
- if used >= math.MaxInt64 {
- used = math.MaxInt64 - 1
- }
- return used, nil
+ return *f.usage.Used, nil
}
// GetNumObjects get the number of objects of the fs
From 7822df565e252a7df89e40d65e6d61a357b28150 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 16:02:02 +0200
Subject: [PATCH 142/560] staticcheck: unused func
---
backend/azureblob/azureblob.go | 13 -------------
backend/drive/drive.go | 6 ------
backend/onedrive/onedrive.go | 10 ----------
backend/uptobox/uptobox.go | 5 -----
4 files changed, 34 deletions(-)
diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go
index 971bdc1d14fd5..a852bac565efd 100644
--- a/backend/azureblob/azureblob.go
+++ b/backend/azureblob/azureblob.go
@@ -1293,19 +1293,6 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
return f.NewObject(ctx, remote)
}
-func (f *Fs) getMemoryPool(size int64) *pool.Pool {
- if size == int64(f.opt.ChunkSize) {
- return f.pool
- }
-
- return pool.New(
- time.Duration(f.opt.MemoryPoolFlushTime),
- int(size),
- f.ci.Transfers,
- f.opt.MemoryPoolUseMmap,
- )
-}
-
// ------------------------------------------------------------
// Fs returns the parent Fs
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index 918faddf95b4e..4e17f09df297d 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -3525,12 +3525,6 @@ func (o *baseObject) Size() int64 {
return o.bytes
}
-// getRemoteInfo returns a drive.File for the remote
-func (f *Fs) getRemoteInfo(ctx context.Context, remote string) (info *drive.File, err error) {
- info, _, _, _, _, err = f.getRemoteInfoWithExport(ctx, remote)
- return
-}
-
// getRemoteInfoWithExport returns a drive.File and the export settings for the remote
func (f *Fs) getRemoteInfoWithExport(ctx context.Context, remote string) (
info *drive.File, extension, exportName, exportMimeType string, isDocument bool, err error) {
diff --git a/backend/onedrive/onedrive.go b/backend/onedrive/onedrive.go
index f8ab22a27480d..4e899addc8601 100644
--- a/backend/onedrive/onedrive.go
+++ b/backend/onedrive/onedrive.go
@@ -1760,16 +1760,6 @@ func (o *Object) rootPath() string {
return o.fs.rootPath(o.remote)
}
-// srvPath returns a path for use in server given a remote
-func (f *Fs) srvPath(remote string) string {
- return f.opt.Enc.FromStandardPath(f.rootSlash() + remote)
-}
-
-// srvPath returns a path for use in server
-func (o *Object) srvPath() string {
- return o.fs.srvPath(o.remote)
-}
-
// Hash returns the SHA-1 of an object returning a lowercase hex string
func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
if o.fs.driveType == driveTypePersonal {
diff --git a/backend/uptobox/uptobox.go b/backend/uptobox/uptobox.go
index e232ac83cd460..88faefce416f7 100644
--- a/backend/uptobox/uptobox.go
+++ b/backend/uptobox/uptobox.go
@@ -918,11 +918,6 @@ func (o *Object) Remote() string {
return o.remote
}
-// Returns the full remote path for the object
-func (o *Object) filePath() string {
- return o.fs.dirPath(o.remote)
-}
-
// ModTime returns the modification time of the object
//
// It attempts to read the objects mtime and if that isn't present the
From 5b579cea47ce46fef393336042b4f1b8184c2640 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 16:33:45 +0200
Subject: [PATCH 143/560] staticcheck: use golang.org/x/text/cases instead of
deprecated strings.Title
strings.Title has been deprecated since Go 1.18 and an alternative has been
available since Go 1.0. The rule Title uses for word boundaries does not handle
Unicode punctuation properly. Use golang.org/x/text/cases instead.
---
cmd/help.go | 5 ++++-
fs/operations/operations_test.go | 4 +++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/cmd/help.go b/cmd/help.go
index b5f1c222707dd..0d1295d34050f 100644
--- a/cmd/help.go
+++ b/cmd/help.go
@@ -17,6 +17,8 @@ import (
"github.com/rclone/rclone/lib/atexit"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
)
// Root is the main rclone command
@@ -316,7 +318,8 @@ func showBackend(name string) {
optionsType = "advanced"
continue
}
- fmt.Printf("### %s options\n\n", strings.Title(optionsType))
+ optionsType = cases.Title(language.Und, cases.NoLower).String(optionsType)
+ fmt.Printf("### %s options\n\n", optionsType)
fmt.Printf("Here are the %s options specific to %s (%s).\n\n", optionsType, backend.Name, backend.Description)
optionsType = "advanced"
for _, opt := range opts {
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 8cd7296e4c2b2..34b88d6d14c03 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -47,6 +47,8 @@ import (
"github.com/rclone/rclone/fstest/fstests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
)
// Some times used in the tests
@@ -270,7 +272,7 @@ func TestHashSums(t *testing.T) {
if !hashes.Contains(test.ht) {
continue
}
- name := strings.Title(test.ht.String())
+ name := cases.Title(language.Und, cases.NoLower).String(test.ht.String())
if test.download {
name += "Download"
}
From 7b8c974decee9f9f673865798a06ac60a0e1ac79 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 17:20:08 +0200
Subject: [PATCH 144/560] staticcheck: ineffective break statement
---
vfs/vfs.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/vfs/vfs.go b/vfs/vfs.go
index fc4eb126442a1..304e23756e3ea 100644
--- a/vfs/vfs.go
+++ b/vfs/vfs.go
@@ -369,7 +369,6 @@ func (vfs *VFS) WaitForWriters(timeout time.Duration) {
tick.Reset(tickTime)
select {
case <-tick.C:
- break
case <-deadline.C:
fs.Errorf(nil, "Exiting even though %d writers active and %d cache items in use after %v\n%s", writers, cacheInUse, timeout, vfs.cache.Dump())
return
From a1fd60ec2b296918befca263fa1986249a4840ef Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 17:20:31 +0200
Subject: [PATCH 145/560] staticcheck: empty branch
---
backend/yandex/yandex.go | 8 ++++----
fstest/fstests/fstests.go | 4 +---
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/backend/yandex/yandex.go b/backend/yandex/yandex.go
index 7eea7187f2223..3097a8bc9ee40 100644
--- a/backend/yandex/yandex.go
+++ b/backend/yandex/yandex.go
@@ -782,11 +782,11 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
}
_, err = f.readMetaDataForPath(ctx, dstPath, &api.ResourceInfoRequestOptions{})
- if apiErr, ok := err.(*api.ErrorResponse); ok {
+ if _, ok := err.(*api.ErrorResponse); ok {
// does not exist
- if apiErr.ErrorName == "DiskNotFoundError" {
- // OK
- }
+ //if apiErr.ErrorName == "DiskNotFoundError" {
+ // // OK
+ //}
} else if err != nil {
return err
} else {
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 286a6787102d2..97e44ea1a298e 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -1459,9 +1459,7 @@ func Run(t *testing.T, opt *Opt) {
assert.Equal(t, metaMimeType, gotMimeType)
}
})
- } else {
- // Have some metadata here we didn't write - can't really check it!
- }
+ } // else: Have some metadata here we didn't write - can't really check it!
})
// TestObjectSetModTime tests that SetModTime works
From 3e9c5eca3bfb3a4d3203126d4b26004fea78146f Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sat, 2 Jul 2022 16:33:04 +0200
Subject: [PATCH 146/560] yandex: handle api error on server-side move
---
backend/yandex/yandex.go | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/backend/yandex/yandex.go b/backend/yandex/yandex.go
index 3097a8bc9ee40..7f0ddc5d8d4df 100644
--- a/backend/yandex/yandex.go
+++ b/backend/yandex/yandex.go
@@ -782,11 +782,10 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
}
_, err = f.readMetaDataForPath(ctx, dstPath, &api.ResourceInfoRequestOptions{})
- if _, ok := err.(*api.ErrorResponse); ok {
- // does not exist
- //if apiErr.ErrorName == "DiskNotFoundError" {
- // // OK
- //}
+ if apiErr, ok := err.(*api.ErrorResponse); ok {
+ if apiErr.ErrorName != "DiskNotFoundError" {
+ return err
+ }
} else if err != nil {
return err
} else {
From c9d67c86fb69e9b0da0087963f9d722a223a2306 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 17:42:18 +0200
Subject: [PATCH 147/560] staticcheck: ignore suggestion to use context.TODO
instead of nil when testing nil Context
---
fs/config_test.go | 2 +-
fs/filter/filter_test.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/fs/config_test.go b/fs/config_test.go
index 9813dc218990c..f9157bbf6e5f3 100644
--- a/fs/config_test.go
+++ b/fs/config_test.go
@@ -11,7 +11,7 @@ func TestGetConfig(t *testing.T) {
ctx := context.Background()
// Check nil
- config := GetConfig(nil)
+ config := GetConfig(nil) //lint:ignore SA1012 we want to test passing a nil Context and therefore ignore lint suggestion of using context.TODO
assert.Equal(t, globalConfig, config)
// Check empty config
diff --git a/fs/filter/filter_test.go b/fs/filter/filter_test.go
index 27da120c4e5e1..aada638fc5031 100644
--- a/fs/filter/filter_test.go
+++ b/fs/filter/filter_test.go
@@ -798,7 +798,7 @@ func TestGetConfig(t *testing.T) {
ctx := context.Background()
// Check nil
- config := GetConfig(nil)
+ config := GetConfig(nil) //lint:ignore SA1012 we want to test passing a nil Context and therefore ignore lint suggestion of using context.TODO
assert.Equal(t, globalConfig, config)
// Check empty config
From 1f9560e8739f14bacaa213ef54dbdcadc661e174 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 18:11:45 +0200
Subject: [PATCH 148/560] selfupdate: replace deprecated x/crypto/openpgp
package with ProtonMail/go-crypto
---
cmd/selfupdate/verify.go | 6 +++---
go.mod | 1 +
go.sum | 3 +++
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/cmd/selfupdate/verify.go b/cmd/selfupdate/verify.go
index 74f87eab538f5..503bbd7f6368d 100644
--- a/cmd/selfupdate/verify.go
+++ b/cmd/selfupdate/verify.go
@@ -10,9 +10,9 @@ import (
"fmt"
"strings"
+ "github.com/ProtonMail/go-crypto/openpgp"
+ "github.com/ProtonMail/go-crypto/openpgp/clearsign"
"github.com/rclone/rclone/fs"
- "golang.org/x/crypto/openpgp"
- "golang.org/x/crypto/openpgp/clearsign"
)
var ncwPublicKeyPGP = `-----BEGIN PGP PUBLIC KEY BLOCK-----
@@ -59,7 +59,7 @@ func verifyHashsum(ctx context.Context, siteURL, version, archive string, hash [
}
block, rest := clearsign.Decode(sumsBuf)
// block.Bytes = block.Bytes[1:] // uncomment to test invalid signature
- _, err = openpgp.CheckDetachedSignature(keyRing, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body)
+ _, err = openpgp.CheckDetachedSignature(keyRing, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body, nil)
if err != nil || len(rest) > 0 {
return errors.New("invalid hashsum signature")
}
diff --git a/go.mod b/go.mod
index e61cdce97dbc6..53eff1fed2622 100644
--- a/go.mod
+++ b/go.mod
@@ -72,6 +72,7 @@ require (
require (
github.com/Microsoft/go-winio v0.5.1 // indirect
+ github.com/ProtonMail/go-crypto v0.0.0-20220623141421-5afb4c282135
github.com/golang-jwt/jwt/v4 v4.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index 9cc774dd30d10..008e5a7f9652c 100644
--- a/go.sum
+++ b/go.sum
@@ -89,6 +89,8 @@ github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpz
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/ProtonMail/go-crypto v0.0.0-20220623141421-5afb4c282135 h1:xDc/cFH/hwyr9KyWc0sm26lpsscqtfZBvU8NpRLHwJ0=
+github.com/ProtonMail/go-crypto v0.0.0-20220623141421-5afb4c282135/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A=
github.com/Unknwon/goconfig v1.0.0/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
@@ -660,6 +662,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
From 9612ca61105c0138eeb939c35cd711f4dc955ced Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 18:34:37 +0200
Subject: [PATCH 149/560] staticcheck: ignore unused if platform dependent
---
cmd/serve/docker/docker.go | 2 +-
cmd/serve/docker/systemd_unsupported.go | 1 +
cmd/serve/ftp/ftp.go | 3 ++-
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/cmd/serve/docker/docker.go b/cmd/serve/docker/docker.go
index b9702d87f3203..5e49718016f0d 100644
--- a/cmd/serve/docker/docker.go
+++ b/cmd/serve/docker/docker.go
@@ -20,7 +20,7 @@ var (
pluginName = "rclone"
pluginScope = "local"
baseDir = "/var/lib/docker-volumes/rclone"
- sockDir = "/run/docker/plugins" // location of unix sockets (only relevant on Linux and FreeBSD)
+ sockDir = "/run/docker/plugins" //lint:ignore U1000 unused when not building linux
defSpecDir = "/etc/docker/plugins"
stateFile = "docker-plugin.state"
socketAddr = "" // TCP listening address or empty string for Unix socket
diff --git a/cmd/serve/docker/systemd_unsupported.go b/cmd/serve/docker/systemd_unsupported.go
index e25d784c035eb..02b0b81a445c6 100644
--- a/cmd/serve/docker/systemd_unsupported.go
+++ b/cmd/serve/docker/systemd_unsupported.go
@@ -7,6 +7,7 @@ import (
"os"
)
+//lint:ignore U1000 unused when not building linux
func systemdActivationFiles() []*os.File {
return nil
}
diff --git a/cmd/serve/ftp/ftp.go b/cmd/serve/ftp/ftp.go
index 05f7b092405d9..7ed96e3c0e385 100644
--- a/cmd/serve/ftp/ftp.go
+++ b/cmd/serve/ftp/ftp.go
@@ -184,7 +184,8 @@ func (s *server) serve() error {
return s.srv.ListenAndServe()
}
-// serve runs the ftp server
+// close stops the ftp server
+//lint:ignore U1000 unused when not building linux
func (s *server) close() error {
fs.Logf(s.f, "Stopping FTP on %s", s.srv.Hostname+":"+strconv.Itoa(s.srv.Port))
return s.srv.Shutdown()
From 92a43c5f7b554de970155e7cf20a693b49c72200 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 18:35:01 +0200
Subject: [PATCH 150/560] staticcheck: should use a simple channel send/receive
instead of select with a single case
---
fs/rc/jobs/job_test.go | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/fs/rc/jobs/job_test.go b/fs/rc/jobs/job_test.go
index 3fcd4530bf94a..cb92e602aeaa8 100644
--- a/fs/rc/jobs/job_test.go
+++ b/fs/rc/jobs/job_test.go
@@ -107,21 +107,17 @@ var shortFn = func(ctx context.Context, in rc.Params) (rc.Params, error) {
}
var ctxFn = func(ctx context.Context, in rc.Params) (rc.Params, error) {
- select {
- case <-ctx.Done():
- return nil, ctx.Err()
- }
+ <-ctx.Done()
+ return nil, ctx.Err()
}
var ctxParmFn = func(paramCtx context.Context, returnError bool) func(ctx context.Context, in rc.Params) (rc.Params, error) {
return func(ctx context.Context, in rc.Params) (rc.Params, error) {
- select {
- case <-paramCtx.Done():
- if returnError {
- return nil, ctx.Err()
- }
- return rc.Params{}, nil
+ <-paramCtx.Done()
+ if returnError {
+ return nil, ctx.Err()
}
+ return rc.Params{}, nil
}
}
From 986bb176561be434a7cdb19df310ebb7d5f45296 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 18:36:46 +0200
Subject: [PATCH 151/560] staticcheck: awserr.BatchError is deprecated:
Replaced with BatchedErrors
---
backend/s3/s3.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index 1693446b08640..448b7c08f00b4 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -4288,7 +4288,7 @@ func (o *Object) uploadMultipart(ctx context.Context, req *s3.PutObjectInput, si
func unWrapAwsError(err error) (found bool, outErr error) {
if awsErr, ok := err.(awserr.Error); ok {
var origErrs []error
- if batchErr, ok := awsErr.(awserr.BatchError); ok {
+ if batchErr, ok := awsErr.(awserr.BatchedErrors); ok {
origErrs = batchErr.OrigErrs()
} else {
origErrs = []error{awsErr.OrigErr()}
From c70e890966debf03c8563d76e9ff865edfeede04 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 18:40:43 +0200
Subject: [PATCH 152/560] staticcheck: TLS config NameToCertificate is
deprecated, should instead let library select the first compatible chain from
Certificates
---
fs/fshttp/http.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/fs/fshttp/http.go b/fs/fshttp/http.go
index 9384d2fa4a9d1..953a313dd59f4 100644
--- a/fs/fshttp/http.go
+++ b/fs/fshttp/http.go
@@ -70,7 +70,6 @@ func NewTransportCustom(ctx context.Context, customize func(*http.Transport)) ht
log.Fatalf("Failed to load --client-cert/--client-key pair: %v", err)
}
t.TLSClientConfig.Certificates = []tls.Certificate{cert}
- t.TLSClientConfig.BuildNameToCertificate()
}
// Load CA cert
From f18095b0044d657719da9b188f0b0e15ed09a358 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 21:43:08 +0200
Subject: [PATCH 153/560] staticcheck: ignore deprecations that are not
relevant
---
lib/structs/structs_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/structs/structs_test.go b/lib/structs/structs_test.go
index 9dffecf3f155c..d9c3bc67833b1 100644
--- a/lib/structs/structs_test.go
+++ b/lib/structs/structs_test.go
@@ -22,8 +22,8 @@ func TestSetDefaults(t *testing.T) {
assert.Equal(t, ptr(old.Proxy), ptr(newT.Proxy), "when checking .Proxy")
assert.Equal(t, ptr(old.DialContext), ptr(newT.DialContext), "when checking .DialContext")
// Check the other public fields
- assert.Equal(t, ptr(old.Dial), ptr(newT.Dial), "when checking .Dial")
- assert.Equal(t, ptr(old.DialTLS), ptr(newT.DialTLS), "when checking .DialTLS")
+ assert.Equal(t, ptr(old.Dial), ptr(newT.Dial), "when checking .Dial") //lint:ignore SA1019 newT.Dial has been deprecated since Go 1.7: Use DialContext instead, which allows the transport to cancel dials as soon as they are no longer needed. If both are set, DialContext takes priority.
+ assert.Equal(t, ptr(old.DialTLS), ptr(newT.DialTLS), "when checking .DialTLS") //lint:ignore SA1019 old.DialTLS has been deprecated since Go 1.14: Use DialTLSContext instead, which allows the transport to cancel dials as soon as they are no longer needed. If both are set, DialTLSContext takes priority.
assert.Equal(t, old.TLSClientConfig, newT.TLSClientConfig, "when checking .TLSClientConfig")
assert.Equal(t, old.TLSHandshakeTimeout, newT.TLSHandshakeTimeout, "when checking .TLSHandshakeTimeout")
assert.Equal(t, old.DisableKeepAlives, newT.DisableKeepAlives, "when checking .DisableKeepAlives")
From e5bf6a813c75a6436041b15b97d624060b598eb1 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Fri, 24 Jun 2022 21:45:38 +0200
Subject: [PATCH 154/560] staticcheck: google api New is deprecated: please use
NewService instead
---
backend/drive/drive.go | 9 +++++----
backend/googlecloudstorage/googlecloudstorage.go | 3 ++-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index 4e17f09df297d..baf2be267e839 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -51,6 +51,7 @@ import (
drive_v2 "google.golang.org/api/drive/v2"
drive "google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
+ "google.golang.org/api/option"
)
// Constants
@@ -1210,13 +1211,13 @@ func newFs(ctx context.Context, name, path string, m configmap.Mapper) (*Fs, err
// Create a new authorized Drive client.
f.client = oAuthClient
- f.svc, err = drive.New(f.client)
+ f.svc, err = drive.NewService(context.Background(), option.WithHTTPClient(f.client))
if err != nil {
return nil, fmt.Errorf("couldn't create Drive client: %w", err)
}
if f.opt.V2DownloadMinSize >= 0 {
- f.v2Svc, err = drive_v2.New(f.client)
+ f.v2Svc, err = drive_v2.NewService(context.Background(), option.WithHTTPClient(f.client))
if err != nil {
return nil, fmt.Errorf("couldn't create Drive v2 client: %w", err)
}
@@ -2992,12 +2993,12 @@ func (f *Fs) changeServiceAccountFile(ctx context.Context, file string) (err err
return fmt.Errorf("drive: failed when making oauth client: %w", err)
}
f.client = oAuthClient
- f.svc, err = drive.New(f.client)
+ f.svc, err = drive.NewService(context.Background(), option.WithHTTPClient(f.client))
if err != nil {
return fmt.Errorf("couldn't create Drive client: %w", err)
}
if f.opt.V2DownloadMinSize >= 0 {
- f.v2Svc, err = drive_v2.New(f.client)
+ f.v2Svc, err = drive_v2.NewService(context.Background(), option.WithHTTPClient(f.client))
if err != nil {
return fmt.Errorf("couldn't create Drive v2 client: %w", err)
}
diff --git a/backend/googlecloudstorage/googlecloudstorage.go b/backend/googlecloudstorage/googlecloudstorage.go
index e010dfd88bdd7..b11771d1b8351 100644
--- a/backend/googlecloudstorage/googlecloudstorage.go
+++ b/backend/googlecloudstorage/googlecloudstorage.go
@@ -44,6 +44,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/googleapi"
+ option "google.golang.org/api/option"
// NOTE: This API is deprecated
storage "google.golang.org/api/storage/v1"
@@ -524,7 +525,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
// Create a new authorized Drive client.
f.client = oAuthClient
- f.svc, err = storage.New(f.client)
+ f.svc, err = storage.NewService(context.Background(), option.WithHTTPClient(f.client))
if err != nil {
return nil, fmt.Errorf("couldn't create Google Cloud Storage client: %w", err)
}
From ad8c94e9820111b612c7d2984be02120f9da0511 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sat, 2 Jul 2022 16:07:54 +0200
Subject: [PATCH 155/560] staticcheck: redundant return statement
---
lib/cache/cache.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/lib/cache/cache.go b/lib/cache/cache.go
index b8d323ff1276a..8e03819bc2152 100644
--- a/lib/cache/cache.go
+++ b/lib/cache/cache.go
@@ -236,7 +236,6 @@ func (c *Cache) Clear() {
delete(c.cache, key)
}
c.mu.Unlock()
- return
}
// Entries returns the number of entries in the cache
From 5c6a958ad88db85471fc8d1975a5378f67e16fc1 Mon Sep 17 00:00:00 2001
From: albertony <12441419+albertony@users.noreply.github.com>
Date: Sat, 2 Jul 2022 16:11:22 +0200
Subject: [PATCH 156/560] go mod tidy: github.com/pkg/xattr should be direct
---
go.mod | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/go.mod b/go.mod
index 53eff1fed2622..8f301a4df5ce3 100644
--- a/go.mod
+++ b/go.mod
@@ -77,7 +77,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/jlaffaye/ftp v0.0.0-20220524001917-dfa1e758f3af
- github.com/pkg/xattr v0.4.7 // indirect
+ github.com/pkg/xattr v0.4.7
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
)
From 3ec07d5db9f932335c1a33e30d08042012a5753d Mon Sep 17 00:00:00 2001
From: Paul Norman
Date: Tue, 5 Jul 2022 20:12:57 -0700
Subject: [PATCH 157/560] docs: fix typo in license webpage
---
MANUAL.html | 2 +-
MANUAL.md | 2 +-
MANUAL.txt | 2 +-
README.md | 2 +-
docs/content/licence.md | 2 +-
rclone.1 | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/MANUAL.html b/MANUAL.html
index 7b39794fbedda..eb2a7d1970566 100644
--- a/MANUAL.html
+++ b/MANUAL.html
@@ -30541,7 +30541,7 @@ Rclone
For example: On a Windows system, you have a file with name Test:1.jpg, where : is the Unicode fullwidth colon symbol. When using rclone to copy this to your Google Drive, you will notice that the file gets renamed to Test:1.jpg, where : is the regular (halfwidth) colon.
The reason for such renames is the way rclone handles different restricted filenames on different cloud storage systems. It tries to avoid ambiguous file names as much and allow moving files between many cloud storage systems transparently, by replacing invalid characters with similar looking Unicode characters when transferring to one storage system, and replacing back again when transferring to a different storage system where the original characters are supported. When the same Unicode characters are intentionally used in file names, this replacement strategy leads to unwanted renames. Read more here.
License
-This is free software under the terms of MIT the license (check the COPYING file included with the source code).
+This is free software under the terms of the MIT license (check the COPYING file included with the source code).
Copyright (C) 2019 by Nick Craig-Wood https://www.craig-wood.com/nick/
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/MANUAL.md b/MANUAL.md
index 06b37f0d201e5..58e4ddad6a03d 100644
--- a/MANUAL.md
+++ b/MANUAL.md
@@ -40150,7 +40150,7 @@ to unwanted renames. Read more [here](https://rclone.org/overview/#restricted-fi
# License
-This is free software under the terms of MIT the license (check the
+This is free software under the terms of the MIT license (check the
COPYING file included with the source code).
```
diff --git a/MANUAL.txt b/MANUAL.txt
index 994bd6612bec6..d25a312cb36eb 100644
--- a/MANUAL.txt
+++ b/MANUAL.txt
@@ -40651,7 +40651,7 @@ replacement strategy leads to unwanted renames. Read more here.
License
-This is free software under the terms of MIT the license (check the
+This is free software under the terms of the MIT license (check the
COPYING file included with the source code).
Copyright (C) 2019 by Nick Craig-Wood https://www.craig-wood.com/nick/
diff --git a/README.md b/README.md
index ba85e16bdd11b..01776699e2f1f 100644
--- a/README.md
+++ b/README.md
@@ -132,5 +132,5 @@ Please see the [rclone website](https://rclone.org/) for:
License
-------
-This is free software under the terms of MIT the license (check the
+This is free software under the terms of the MIT license (check the
[COPYING file](/COPYING) included in this package).
diff --git a/docs/content/licence.md b/docs/content/licence.md
index a12d71de4ea5a..004522223c0c2 100644
--- a/docs/content/licence.md
+++ b/docs/content/licence.md
@@ -5,7 +5,7 @@ description: "Rclone Licence"
# License
-This is free software under the terms of MIT the license (check the
+This is free software under the terms of the MIT license (check the
COPYING file included with the source code).
```
diff --git a/rclone.1 b/rclone.1
index 157f41d569313..693b003af1d88 100644
--- a/rclone.1
+++ b/rclone.1
@@ -59039,7 +59039,7 @@ Read more
here (https://rclone.org/overview/#restricted-filenames-caveats).
.SH License
.PP
-This is free software under the terms of MIT the license (check the
+This is free software under the terms of the MIT license (check the
COPYING file included with the source code).
.IP
.nf
From b5efffee9d53eb6e7801011846cb248feb9c83a7 Mon Sep 17 00:00:00 2001
From: Lorenzo Maiorfi
Date: Wed, 6 Jul 2022 12:54:04 +0200
Subject: [PATCH 158/560] azureblob: allow remote emulator (azurite) - fixes
#6290
---
backend/azureblob/azureblob.go | 6 +++++-
docs/content/azureblob.md | 9 +++++----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go
index a852bac565efd..5597ed21fd6c1 100644
--- a/backend/azureblob/azureblob.go
+++ b/backend/azureblob/azureblob.go
@@ -600,7 +600,11 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
if err != nil {
return nil, fmt.Errorf("failed to parse credentials: %w", err)
}
- u, err = url.Parse(emulatorBlobEndpoint)
+ var actualEmulatorEndpoint = emulatorBlobEndpoint
+ if opt.Endpoint != "" {
+ actualEmulatorEndpoint = opt.Endpoint
+ }
+ u, err = url.Parse(actualEmulatorEndpoint)
if err != nil {
return nil, fmt.Errorf("failed to make azure storage url from account and endpoint: %w", err)
}
diff --git a/docs/content/azureblob.md b/docs/content/azureblob.md
index a8151912bc44d..fa2205ae58993 100644
--- a/docs/content/azureblob.md
+++ b/docs/content/azureblob.md
@@ -526,7 +526,8 @@ See [List of backends that do not support rclone about](https://rclone.org/overv
## Azure Storage Emulator Support
-You can test rclone with storage emulator locally, to do this make sure azure storage emulator
-installed locally and set up a new remote with `rclone config` follow instructions described in
-introduction, set `use_emulator` config as `true`, you do not need to provide default account name
-or key if using emulator.
+You can run rclone with storage emulator (usually _azurite_).
+
+To do this, just set up a new remote with `rclone config` following instructions described in introduction and set `use_emulator` config as `true`. You do not need to provide default account name neither an account key.
+
+Also, if you want to access a storage emulator instance running on a different machine, you can override _Endpoint_ parameter in advanced settings, setting it to `http(s)://:/devstoreaccount1` (e.g. `http://10.254.2.5:10000/devstoreaccount1`).
From 388da82762ba24cef13c995d572ea9dc55dcf9aa Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 7 Jul 2022 16:08:59 +0100
Subject: [PATCH 159/560] Add Anthrazz to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 66f33c154de12..191a7aad6eeee 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -620,3 +620,4 @@ put them back in again.` >}}
* buda
* mirekphd <36706320+mirekphd@users.noreply.github.com>
* vyloy
+ * Anthrazz <25553648+Anthrazz@users.noreply.github.com>
From 0db50ecb2f3111d0a91284ddcecb4a5d861382f5 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 7 Jul 2022 16:08:59 +0100
Subject: [PATCH 160/560] Add zzr93 to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 191a7aad6eeee..29d29693d81ad 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -621,3 +621,4 @@ put them back in again.` >}}
* mirekphd <36706320+mirekphd@users.noreply.github.com>
* vyloy
* Anthrazz <25553648+Anthrazz@users.noreply.github.com>
+ * zzr93 <34027824+zzr93@users.noreply.github.com>
From a9c531b9eb3ba2651b466351fd45448fb62f1bbd Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 7 Jul 2022 16:08:59 +0100
Subject: [PATCH 161/560] Add Paul Norman to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 29d29693d81ad..de83c68df95b3 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -622,3 +622,4 @@ put them back in again.` >}}
* vyloy
* Anthrazz <25553648+Anthrazz@users.noreply.github.com>
* zzr93 <34027824+zzr93@users.noreply.github.com>
+ * Paul Norman
From 2515039e18d694e9ef02a41a7b2f42ca1cd9bbfd Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 7 Jul 2022 16:08:59 +0100
Subject: [PATCH 162/560] Add Lorenzo Maiorfi to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index de83c68df95b3..d4129e3756f09 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -623,3 +623,4 @@ put them back in again.` >}}
* Anthrazz <25553648+Anthrazz@users.noreply.github.com>
* zzr93 <34027824+zzr93@users.noreply.github.com>
* Paul Norman
+ * Lorenzo Maiorfi
From 2e54b56a01b14da2e488ffb3ffde47b399f08866 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Thu, 7 Jul 2022 11:31:53 +0100
Subject: [PATCH 163/560] rcat: check checksums by default like copy does #6305
Before this change we were calculating the checksum for an rcat
transfer but never checking it.
See: https://forum.rclone.org/t/optimize-rclone-on-raspberry-pi-4-8gb/31741
---
fs/operations/operations.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index e00a20a9e066f..0b0f9f75e01d9 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -1391,11 +1391,14 @@ func Rcat(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser,
compare := func(dst fs.Object) error {
var sums map[hash.Type]string
+ opt := defaultEqualOpt(ctx)
if hasher != nil {
+ // force --checksum on if we have hashes
+ opt.checkSum = true
sums = hasher.Sums()
}
src := object.NewStaticObjectInfo(dstFileName, modTime, int64(readCounter.BytesRead()), false, sums, fdst)
- if !Equal(ctx, src, dst) {
+ if !equal(ctx, src, dst, opt) {
err = fmt.Errorf("corrupted on transfer")
err = fs.CountError(err)
fs.Errorf(dst, "%v", err)
From 62bcc84f6fc437e310599d4e5b4692f45bab0d4a Mon Sep 17 00:00:00 2001
From: Claudio Maradonna
Date: Tue, 5 Jul 2022 18:29:14 +0200
Subject: [PATCH 164/560] vfs: add --vfs-disk-space-total-size option to
manually set the total disk space
Now you can specify --vfs-disk-space-total-size to set the total disk
space (default to -1)
fixes #3270
---
vfs/help.go | 7 +++
vfs/vfs.go | 6 +++
vfs/vfscommon/options.go | 96 ++++++++++++++++++++--------------------
vfs/vfsflags/vfsflags.go | 1 +
4 files changed, 63 insertions(+), 47 deletions(-)
diff --git a/vfs/help.go b/vfs/help.go
index 01382cf5a1770..3bb809ca8b010 100644
--- a/vfs/help.go
+++ b/vfs/help.go
@@ -307,6 +307,13 @@ If the flag is not provided on the command line, then its default value depends
on the operating system where rclone runs: "true" on Windows and macOS, "false"
otherwise. If the flag is provided without a value, then it is "true".
+### VFS Disk Options
+
+This flag allows you to manually set the statistics about the filing system.
+It can be useful when those statistics cannot be read correctly automatically.
+
+ --vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
+
### Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used.
diff --git a/vfs/vfs.go b/vfs/vfs.go
index 304e23756e3ea..b6dc141b3f854 100644
--- a/vfs/vfs.go
+++ b/vfs/vfs.go
@@ -604,6 +604,7 @@ func (vfs *VFS) Statfs() (total, used, free int64) {
return
}
}
+
if u := vfs.usage; u != nil {
if u.Total != nil {
total = *u.Total
@@ -615,6 +616,11 @@ func (vfs *VFS) Statfs() (total, used, free int64) {
used = *u.Used
}
}
+
+ if int64(vfs.Opt.DiskSpaceTotalSize) >= 0 {
+ total = int64(vfs.Opt.DiskSpaceTotalSize)
+ }
+
total, used, free = fillInMissingSizes(total, used, free, unknownFreeBytes)
return
}
diff --git a/vfs/vfscommon/options.go b/vfs/vfscommon/options.go
index 5c1ae7900e669..c3631859ad0ae 100644
--- a/vfs/vfscommon/options.go
+++ b/vfs/vfscommon/options.go
@@ -10,57 +10,59 @@ import (
// Options is options for creating the vfs
type Options struct {
- NoSeek bool // don't allow seeking if set
- NoChecksum bool // don't check checksums if set
- ReadOnly bool // if set VFS is read only
- NoModTime bool // don't read mod times for files
- DirCacheTime time.Duration // how long to consider directory listing cache valid
- PollInterval time.Duration
- Umask int
- UID uint32
- GID uint32
- DirPerms os.FileMode
- FilePerms os.FileMode
- ChunkSize fs.SizeSuffix // if > 0 read files in chunks
- ChunkSizeLimit fs.SizeSuffix // if > ChunkSize double the chunk size after each chunk until reached
- CacheMode CacheMode
- CacheMaxAge time.Duration
- CacheMaxSize fs.SizeSuffix
- CachePollInterval time.Duration
- CaseInsensitive bool
- WriteWait time.Duration // time to wait for in-sequence write
- ReadWait time.Duration // time to wait for in-sequence read
- WriteBack time.Duration // time to wait before writing back dirty files
- ReadAhead fs.SizeSuffix // bytes to read ahead in cache mode "full"
- UsedIsSize bool // if true, use the `rclone size` algorithm for Used size
- FastFingerprint bool // if set use fast fingerprints
+ NoSeek bool // don't allow seeking if set
+ NoChecksum bool // don't check checksums if set
+ ReadOnly bool // if set VFS is read only
+ NoModTime bool // don't read mod times for files
+ DirCacheTime time.Duration // how long to consider directory listing cache valid
+ PollInterval time.Duration
+ Umask int
+ UID uint32
+ GID uint32
+ DirPerms os.FileMode
+ FilePerms os.FileMode
+ ChunkSize fs.SizeSuffix // if > 0 read files in chunks
+ ChunkSizeLimit fs.SizeSuffix // if > ChunkSize double the chunk size after each chunk until reached
+ CacheMode CacheMode
+ CacheMaxAge time.Duration
+ CacheMaxSize fs.SizeSuffix
+ CachePollInterval time.Duration
+ CaseInsensitive bool
+ WriteWait time.Duration // time to wait for in-sequence write
+ ReadWait time.Duration // time to wait for in-sequence read
+ WriteBack time.Duration // time to wait before writing back dirty files
+ ReadAhead fs.SizeSuffix // bytes to read ahead in cache mode "full"
+ UsedIsSize bool // if true, use the `rclone size` algorithm for Used size
+ FastFingerprint bool // if set use fast fingerprints
+ DiskSpaceTotalSize fs.SizeSuffix
}
// DefaultOpt is the default values uses for Opt
var DefaultOpt = Options{
- NoModTime: false,
- NoChecksum: false,
- NoSeek: false,
- DirCacheTime: 5 * 60 * time.Second,
- PollInterval: time.Minute,
- ReadOnly: false,
- Umask: 0,
- UID: ^uint32(0), // these values instruct WinFSP-FUSE to use the current user
- GID: ^uint32(0), // overridden for non windows in mount_unix.go
- DirPerms: os.FileMode(0777),
- FilePerms: os.FileMode(0666),
- CacheMode: CacheModeOff,
- CacheMaxAge: 3600 * time.Second,
- CachePollInterval: 60 * time.Second,
- ChunkSize: 128 * fs.Mebi,
- ChunkSizeLimit: -1,
- CacheMaxSize: -1,
- CaseInsensitive: runtime.GOOS == "windows" || runtime.GOOS == "darwin", // default to true on Windows and Mac, false otherwise
- WriteWait: 1000 * time.Millisecond,
- ReadWait: 20 * time.Millisecond,
- WriteBack: 5 * time.Second,
- ReadAhead: 0 * fs.Mebi,
- UsedIsSize: false,
+ NoModTime: false,
+ NoChecksum: false,
+ NoSeek: false,
+ DirCacheTime: 5 * 60 * time.Second,
+ PollInterval: time.Minute,
+ ReadOnly: false,
+ Umask: 0,
+ UID: ^uint32(0), // these values instruct WinFSP-FUSE to use the current user
+ GID: ^uint32(0), // overridden for non windows in mount_unix.go
+ DirPerms: os.FileMode(0777),
+ FilePerms: os.FileMode(0666),
+ CacheMode: CacheModeOff,
+ CacheMaxAge: 3600 * time.Second,
+ CachePollInterval: 60 * time.Second,
+ ChunkSize: 128 * fs.Mebi,
+ ChunkSizeLimit: -1,
+ CacheMaxSize: -1,
+ CaseInsensitive: runtime.GOOS == "windows" || runtime.GOOS == "darwin", // default to true on Windows and Mac, false otherwise
+ WriteWait: 1000 * time.Millisecond,
+ ReadWait: 20 * time.Millisecond,
+ WriteBack: 5 * time.Second,
+ ReadAhead: 0 * fs.Mebi,
+ UsedIsSize: false,
+ DiskSpaceTotalSize: -1,
}
// Init the options, making sure everything is withing range
diff --git a/vfs/vfsflags/vfsflags.go b/vfs/vfsflags/vfsflags.go
index 69efab86c8c6c..ccdcb17db7162 100644
--- a/vfs/vfsflags/vfsflags.go
+++ b/vfs/vfsflags/vfsflags.go
@@ -39,5 +39,6 @@ func AddFlags(flagSet *pflag.FlagSet) {
flags.FVarP(flagSet, &Opt.ReadAhead, "vfs-read-ahead", "", "Extra read ahead over --buffer-size when using cache-mode full")
flags.BoolVarP(flagSet, &Opt.UsedIsSize, "vfs-used-is-size", "", Opt.UsedIsSize, "Use the `rclone size` algorithm for Used size")
flags.BoolVarP(flagSet, &Opt.FastFingerprint, "vfs-fast-fingerprint", "", Opt.FastFingerprint, "Use fast (less accurate) fingerprints for change detection")
+ flags.FVarP(flagSet, &Opt.DiskSpaceTotalSize, "vfs-disk-space-total-size", "", "Specify the total space of disk")
platformFlags(flagSet)
}
From 53400d7edcfe0f8b78584e888dd336bc88fdc651 Mon Sep 17 00:00:00 2001
From: Ovidiu Victor Tatar
Date: Wed, 3 Mar 2021 11:33:29 +0100
Subject: [PATCH 165/560] oauthlib: add method to set a token as expired
This can be used by backends to trigger a refresh of an access token if
they detect an invalid token.
---
lib/oauthutil/oauthutil.go | 17 +++++++++++++++++
lib/oauthutil/renew.go | 5 +++++
2 files changed, 22 insertions(+)
diff --git a/lib/oauthutil/oauthutil.go b/lib/oauthutil/oauthutil.go
index 53a08900c0bcc..2033ac5f55184 100644
--- a/lib/oauthutil/oauthutil.go
+++ b/lib/oauthutil/oauthutil.go
@@ -269,6 +269,23 @@ func (ts *TokenSource) Invalidate() {
ts.mu.Unlock()
}
+// Expire marks the token as expired
+//
+// This also marks the token in the config file as expired, if it is the same one
+func (ts *TokenSource) Expire() error {
+ ts.mu.Lock()
+ defer ts.mu.Unlock()
+ ts.token.Expiry = time.Now().Add(time.Hour * (-1)) // expire token
+ t, err := GetToken(ts.name, ts.m)
+ if err != nil {
+ return err
+ }
+ if t.AccessToken == ts.token.AccessToken {
+ err = PutToken(ts.name, ts.m, ts.token, false)
+ }
+ return err
+}
+
// timeToExpiry returns how long until the token expires
//
// Call with the lock held
diff --git a/lib/oauthutil/renew.go b/lib/oauthutil/renew.go
index 1fb96e77129c2..29786cf69aaf3 100644
--- a/lib/oauthutil/renew.go
+++ b/lib/oauthutil/renew.go
@@ -67,3 +67,8 @@ func (r *Renew) Stop() {
func (r *Renew) Invalidate() {
r.ts.Invalidate()
}
+
+// Expire expires the token source
+func (r *Renew) Expire() error {
+ return r.ts.Expire()
+}
From 502226bfc8c681e556eabbcd88d5c6e695b6434b Mon Sep 17 00:00:00 2001
From: Ovidiu Victor Tatar
Date: Thu, 7 Jul 2022 19:58:21 +0200
Subject: [PATCH 166/560] pacer: add ZeroDelayCalculator
---
lib/pacer/pacers.go | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/lib/pacer/pacers.go b/lib/pacer/pacers.go
index d510eea6436c2..94ef86ccbcf79 100644
--- a/lib/pacer/pacers.go
+++ b/lib/pacer/pacers.go
@@ -104,6 +104,15 @@ func (c *Default) Calculate(state State) time.Duration {
return sleepTime
}
+// ZeroDelayCalculator is a Calculator that never delays.
+type ZeroDelayCalculator struct {
+}
+
+// Calculate takes the current Pacer state and return the wait time until the next try.
+func (c *ZeroDelayCalculator) Calculate(state State) time.Duration {
+ return 0
+}
+
// AmazonCloudDrive is a specialized pacer for Amazon Drive
//
// It implements a truncated exponential backoff strategy with randomization.
From b4d847cadd782b2ea4cc497a88b75ac6c2292e0b Mon Sep 17 00:00:00 2001
From: Ovidiu Victor Tatar
Date: Thu, 7 Jul 2022 19:58:22 +0200
Subject: [PATCH 167/560] new backend: hidrive - fixes #1069
---
README.md | 1 +
backend/all/all.go | 1 +
backend/hidrive/api/queries.go | 81 ++
backend/hidrive/api/types.go | 135 +++
backend/hidrive/helpers.go | 888 +++++++++++++++
backend/hidrive/hidrive.go | 1002 +++++++++++++++++
backend/hidrive/hidrive_test.go | 45 +
backend/hidrive/hidrivehash/hidrivehash.go | 410 +++++++
.../hidrive/hidrivehash/hidrivehash_test.go | 395 +++++++
.../hidrive/hidrivehash/internal/internal.go | 17 +
bin/make_manual.py | 1 +
docs/content/_index.md | 1 +
docs/content/docs.md | 1 +
docs/content/hidrive.md | 461 ++++++++
docs/content/overview.md | 7 +
docs/layouts/chrome/navbar.html | 1 +
fstest/test_all/config.yaml | 3 +
17 files changed, 3450 insertions(+)
create mode 100644 backend/hidrive/api/queries.go
create mode 100644 backend/hidrive/api/types.go
create mode 100644 backend/hidrive/helpers.go
create mode 100644 backend/hidrive/hidrive.go
create mode 100644 backend/hidrive/hidrive_test.go
create mode 100644 backend/hidrive/hidrivehash/hidrivehash.go
create mode 100644 backend/hidrive/hidrivehash/hidrivehash_test.go
create mode 100644 backend/hidrive/hidrivehash/internal/internal.go
create mode 100644 docs/content/hidrive.md
diff --git a/README.md b/README.md
index 01776699e2f1f..7f2302f8998b5 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ Rclone *("rsync for cloud storage")* is a command-line program to sync files and
* Google Drive [:page_facing_up:](https://rclone.org/drive/)
* Google Photos [:page_facing_up:](https://rclone.org/googlephotos/)
* HDFS (Hadoop Distributed Filesystem) [:page_facing_up:](https://rclone.org/hdfs/)
+ * HiDrive [:page_facing_up:](https://rclone.org/hidrive/)
* HTTP [:page_facing_up:](https://rclone.org/http/)
* Huawei Cloud Object Storage Service(OBS) [:page_facing_up:](https://rclone.org/s3/#huawei-obs)
* Hubic [:page_facing_up:](https://rclone.org/hubic/)
diff --git a/backend/all/all.go b/backend/all/all.go
index 44414b2879dd7..dc17911e4c349 100644
--- a/backend/all/all.go
+++ b/backend/all/all.go
@@ -21,6 +21,7 @@ import (
_ "github.com/rclone/rclone/backend/googlephotos"
_ "github.com/rclone/rclone/backend/hasher"
_ "github.com/rclone/rclone/backend/hdfs"
+ _ "github.com/rclone/rclone/backend/hidrive"
_ "github.com/rclone/rclone/backend/http"
_ "github.com/rclone/rclone/backend/hubic"
_ "github.com/rclone/rclone/backend/internetarchive"
diff --git a/backend/hidrive/api/queries.go b/backend/hidrive/api/queries.go
new file mode 100644
index 0000000000000..57a1477c1ebdb
--- /dev/null
+++ b/backend/hidrive/api/queries.go
@@ -0,0 +1,81 @@
+package api
+
+import (
+ "encoding/json"
+ "net/url"
+ "path"
+ "strings"
+ "time"
+)
+
+// Some presets for different amounts of information that can be requested for fields;
+// it is recommended to only request the information that is actually needed.
+var (
+ HiDriveObjectNoMetadataFields = []string{"name", "type"}
+ HiDriveObjectWithMetadataFields = append(HiDriveObjectNoMetadataFields, "id", "size", "mtime", "chash")
+ HiDriveObjectWithDirectoryMetadataFields = append(HiDriveObjectWithMetadataFields, "nmembers")
+ DirectoryContentFields = []string{"nmembers"}
+)
+
+// QueryParameters represents the parameters passed to an API-call.
+type QueryParameters struct {
+ url.Values
+}
+
+// NewQueryParameters initializes an instance of QueryParameters and
+// returns a pointer to it.
+func NewQueryParameters() *QueryParameters {
+ return &QueryParameters{url.Values{}}
+}
+
+// SetFileInDirectory sets the appropriate parameters
+// to specify a path to a file in a directory.
+// This is used by requests that work with paths for files that do not exist yet.
+// (For example when creating a file).
+// Most requests use the format produced by SetPath(...).
+func (p *QueryParameters) SetFileInDirectory(filePath string) {
+ directory, file := path.Split(path.Clean(filePath))
+ p.Set("dir", path.Clean(directory))
+ p.Set("name", file)
+ // NOTE: It would be possible to switch to pid-based requests
+ // by modifying this function.
+}
+
+// SetPath sets the appropriate parameters to access the given path.
+func (p *QueryParameters) SetPath(objectPath string) {
+ p.Set("path", path.Clean(objectPath))
+ // NOTE: It would be possible to switch to pid-based requests
+ // by modifying this function.
+}
+
+// SetTime sets the key to the time-value. It replaces any existing values.
+func (p *QueryParameters) SetTime(key string, value time.Time) error {
+ valueAPI := Time(value)
+ valueBytes, err := json.Marshal(&valueAPI)
+ if err != nil {
+ return err
+ }
+ p.Set(key, string(valueBytes))
+ return nil
+}
+
+// AddList adds the given values as a list
+// with each value separated by the separator.
+// It appends to any existing values associated with key.
+func (p *QueryParameters) AddList(key string, separator string, values ...string) {
+ original := p.Get(key)
+ p.Set(key, strings.Join(values, separator))
+ if original != "" {
+ p.Set(key, original+separator+p.Get(key))
+ }
+}
+
+// AddFields sets the appropriate parameter to access the given fields.
+// The given fields will be appended to any other existing fields.
+func (p *QueryParameters) AddFields(prefix string, fields ...string) {
+ modifiedFields := make([]string, len(fields))
+ for i, field := range fields {
+ modifiedFields[i] = prefix + field
+ }
+ p.AddList("fields", ",", modifiedFields...)
+}
diff --git a/backend/hidrive/api/types.go b/backend/hidrive/api/types.go
new file mode 100644
index 0000000000000..4cc912a7285bd
--- /dev/null
+++ b/backend/hidrive/api/types.go
@@ -0,0 +1,135 @@
+// Package api has type definitions and code related to API-calls for the HiDrive-API.
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "strconv"
+ "time"
+)
+
+// Time represents date and time information for the API.
+type Time time.Time
+
+// MarshalJSON turns Time into JSON (in Unix-time/UTC).
+func (t *Time) MarshalJSON() ([]byte, error) {
+ secs := time.Time(*t).Unix()
+ return []byte(strconv.FormatInt(secs, 10)), nil
+}
+
+// UnmarshalJSON turns JSON into Time.
+func (t *Time) UnmarshalJSON(data []byte) error {
+ secs, err := strconv.ParseInt(string(data), 10, 64)
+ if err != nil {
+ return err
+ }
+ *t = Time(time.Unix(secs, 0))
+ return nil
+}
+
+// Error is returned from the API when things go wrong.
+type Error struct {
+ Code json.Number `json:"code"`
+ ContextInfo json.RawMessage
+ Message string `json:"msg"`
+}
+
+// Error returns a string for the error and satisfies the error interface.
+func (e *Error) Error() string {
+ out := fmt.Sprintf("Error %q", e.Code.String())
+ if e.Message != "" {
+ out += ": " + e.Message
+ }
+ if e.ContextInfo != nil {
+ out += fmt.Sprintf(" (%+v)", e.ContextInfo)
+ }
+ return out
+}
+
+// Check Error satisfies the error interface.
+var _ error = (*Error)(nil)
+
+// possible types for HiDriveObject
+const (
+ HiDriveObjectTypeDirectory = "dir"
+ HiDriveObjectTypeFile = "file"
+ HiDriveObjectTypeSymlink = "symlink"
+)
+
+// HiDriveObject describes a folder, a symlink or a file.
+// Depending on the type and content, not all fields are present.
+type HiDriveObject struct {
+ Type string `json:"type"`
+ ID string `json:"id"`
+ ParentID string `json:"parent_id"`
+ Name string `json:"name"`
+ Path string `json:"path"`
+ Size int64 `json:"size"`
+ MemberCount int64 `json:"nmembers"`
+ ModifiedAt Time `json:"mtime"`
+ ChangedAt Time `json:"ctime"`
+ MetaHash string `json:"mhash"`
+ MetaOnlyHash string `json:"mohash"`
+ NameHash string `json:"nhash"`
+ ContentHash string `json:"chash"`
+ IsTeamfolder bool `json:"teamfolder"`
+ Readable bool `json:"readable"`
+ Writable bool `json:"writable"`
+ Shareable bool `json:"shareable"`
+ MIMEType string `json:"mime_type"`
+}
+
+// ModTime returns the modification time of the HiDriveObject.
+func (i *HiDriveObject) ModTime() time.Time {
+ t := time.Time(i.ModifiedAt)
+ if t.IsZero() {
+ t = time.Time(i.ChangedAt)
+ }
+ return t
+}
+
+// UnmarshalJSON turns JSON into HiDriveObject and
+// introduces specific default-values where necessary.
+func (i *HiDriveObject) UnmarshalJSON(data []byte) error {
+ type objectAlias HiDriveObject
+ defaultObject := objectAlias{
+ Size: -1,
+ MemberCount: -1,
+ }
+
+ err := json.Unmarshal(data, &defaultObject)
+ if err != nil {
+ return err
+ }
+ name, err := url.PathUnescape(defaultObject.Name)
+ if err == nil {
+ defaultObject.Name = name
+ }
+
+ *i = HiDriveObject(defaultObject)
+ return nil
+}
+
+// DirectoryContent describes the content of a directory.
+type DirectoryContent struct {
+ TotalCount int64 `json:"nmembers"`
+ Entries []HiDriveObject `json:"members"`
+}
+
+// UnmarshalJSON turns JSON into DirectoryContent and
+// introduces specific default-values where necessary.
+func (d *DirectoryContent) UnmarshalJSON(data []byte) error {
+ type directoryContentAlias DirectoryContent
+ defaultDirectoryContent := directoryContentAlias{
+ TotalCount: -1,
+ }
+
+ err := json.Unmarshal(data, &defaultDirectoryContent)
+ if err != nil {
+ return err
+ }
+
+ *d = DirectoryContent(defaultDirectoryContent)
+ return nil
+}
diff --git a/backend/hidrive/helpers.go b/backend/hidrive/helpers.go
new file mode 100644
index 0000000000000..7b925e1b34722
--- /dev/null
+++ b/backend/hidrive/helpers.go
@@ -0,0 +1,888 @@
+package hidrive
+
+// This file is for helper-functions which may provide more general and
+// specialized functionality than the generic interfaces.
+// There are two sections:
+// 1. methods bound to Fs
+// 2. other functions independent from Fs used throughout the package
+
+// NOTE: Functions accessing paths expect any relative paths
+// to be resolved prior to execution with resolvePath(...).
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "io"
+ "net/http"
+ "path"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/rclone/rclone/backend/hidrive/api"
+ "github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fs/accounting"
+ "github.com/rclone/rclone/fs/fserrors"
+ "github.com/rclone/rclone/lib/ranges"
+ "github.com/rclone/rclone/lib/readers"
+ "github.com/rclone/rclone/lib/rest"
+ "golang.org/x/sync/errgroup"
+ "golang.org/x/sync/semaphore"
+)
+
+const (
+ // MaximumUploadBytes represents the maximum amount of bytes
+ // a single upload-operation will support.
+ MaximumUploadBytes = 2147483647 // = 2GiB - 1
+ // iterationChunkSize represents the chunk size used to iterate directory contents.
+ iterationChunkSize = 5000
+)
+
+var (
+ // retryErrorCodes is a slice of error codes that we will always retry.
+ retryErrorCodes = []int{
+ 429, // Too Many Requests
+ 500, // Internal Server Error
+ 502, // Bad Gateway
+ 503, // Service Unavailable
+ 504, // Gateway Timeout
+ 509, // Bandwidth Limit Exceeded
+ }
+ // ErrorFileExists is returned when a query tries to create a file
+ // that already exists.
+ ErrorFileExists = errors.New("destination file already exists")
+)
+
+// MemberType represents the possible types of entries a directory can contain.
+type MemberType string
+
+// possible values for MemberType
+const (
+ AllMembers MemberType = "all"
+ NoMembers MemberType = "none"
+ DirectoryMembers MemberType = api.HiDriveObjectTypeDirectory
+ FileMembers MemberType = api.HiDriveObjectTypeFile
+ SymlinkMembers MemberType = api.HiDriveObjectTypeSymlink
+)
+
+// SortByField represents possible fields to sort entries of a directory by.
+type SortByField string
+
+// possible values for SortByField
+const (
+ descendingSort string = "-"
+ SortByName SortByField = "name"
+ SortByModTime SortByField = "mtime"
+ SortByObjectType SortByField = "type"
+ SortBySize SortByField = "size"
+ SortByNameDescending SortByField = SortByField(descendingSort) + SortByName
+ SortByModTimeDescending SortByField = SortByField(descendingSort) + SortByModTime
+ SortByObjectTypeDescending SortByField = SortByField(descendingSort) + SortByObjectType
+ SortBySizeDescending SortByField = SortByField(descendingSort) + SortBySize
+)
+
+var (
+ // Unsorted disables sorting and can therefore not be combined with other values.
+ Unsorted = []SortByField{"none"}
+ // DefaultSorted does not specify how to sort and
+ // therefore implies the default sort order.
+ DefaultSorted = []SortByField{}
+)
+
+// CopyOrMoveOperationType represents the possible types of copy- and move-operations.
+type CopyOrMoveOperationType int
+
+// possible values for CopyOrMoveOperationType
+const (
+ MoveOriginal CopyOrMoveOperationType = iota
+ CopyOriginal
+ CopyOriginalPreserveModTime
+)
+
+// OnExistAction represents possible actions the API should take,
+// when a request tries to create a path that already exists.
+type OnExistAction string
+
+// possible values for OnExistAction
+const (
+ // IgnoreOnExist instructs the API not to execute
+ // the request in case of a conflict, but to return an error.
+ IgnoreOnExist OnExistAction = "ignore"
+ // AutoNameOnExist instructs the API to automatically rename
+ // any conflicting request-objects.
+ AutoNameOnExist OnExistAction = "autoname"
+ // OverwriteOnExist instructs the API to overwrite any conflicting files.
+ // This can only be used, if the request operates on files directly.
+ // (For example when moving/copying a file.)
+ // For most requests this action will simply be ignored.
+ OverwriteOnExist OnExistAction = "overwrite"
+)
+
+// shouldRetry returns a boolean as to whether this resp and err deserve to be retried.
+// It tries to expire/invalidate the token, if necessary.
+// It returns the err as a convenience.
+func (f *Fs) shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, error) {
+ if fserrors.ContextError(ctx, &err) {
+ return false, err
+ }
+ if resp != nil && (resp.StatusCode == 401 || isHTTPError(err, 401)) && len(resp.Header["Www-Authenticate"]) > 0 {
+ fs.Debugf(f, "Token might be invalid: %v", err)
+ if f.tokenRenewer != nil {
+ iErr := f.tokenRenewer.Expire()
+ if iErr == nil {
+ return true, err
+ }
+ }
+ }
+ return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
+}
+
+// resolvePath resolves the given (relative) path and
+// returns a path suitable for API-calls.
+// This will consider the root-path of the fs and any needed prefixes.
+//
+// Any relative paths passed to functions that access these paths should
+// be resolved with this first!
+func (f *Fs) resolvePath(objectPath string) string {
+ resolved := path.Join(f.opt.RootPrefix, f.root, f.opt.Enc.FromStandardPath(objectPath))
+ return resolved
+}
+
+// iterateOverDirectory calls the given function callback
+// on each item found in a given directory.
+//
+// If callback ever returns true then this exits early with found = true.
+func (f *Fs) iterateOverDirectory(ctx context.Context, directory string, searchOnly MemberType, callback func(*api.HiDriveObject) bool, fields []string, sortBy []SortByField) (found bool, err error) {
+ parameters := api.NewQueryParameters()
+ parameters.SetPath(directory)
+ parameters.AddFields("members.", fields...)
+ parameters.AddFields("", api.DirectoryContentFields...)
+ parameters.Set("members", string(searchOnly))
+ for _, v := range sortBy {
+ // The explicit conversion is necessary for each element.
+ parameters.AddList("sort", ",", string(v))
+ }
+
+ opts := rest.Opts{
+ Method: "GET",
+ Path: "/dir",
+ Parameters: parameters.Values,
+ }
+
+ iterateContent := func(result *api.DirectoryContent, err error) (bool, error) {
+ if err != nil {
+ return false, err
+ }
+ for _, item := range result.Entries {
+ item.Name = f.opt.Enc.ToStandardName(item.Name)
+ if callback(&item) {
+ return true, nil
+ }
+ }
+ return false, nil
+ }
+ return f.paginateDirectoryAccess(ctx, &opts, iterationChunkSize, 0, iterateContent)
+}
+
+// paginateDirectoryAccess executes requests specified via ctx and opts
+// which should produce api.DirectoryContent.
+// This will paginate the requests using limit starting at the given offset.
+//
+// The given function callback is called on each api.DirectoryContent found
+// along with any errors that occurred.
+// If callback ever returns true then this exits early with found = true.
+// If callback ever returns an error then this exits early with that error.
+func (f *Fs) paginateDirectoryAccess(ctx context.Context, opts *rest.Opts, limit int64, offset int64, callback func(*api.DirectoryContent, error) (bool, error)) (found bool, err error) {
+ for {
+ opts.Parameters.Set("limit", strconv.FormatInt(offset, 10)+","+strconv.FormatInt(limit, 10))
+
+ var result api.DirectoryContent
+ var resp *http.Response
+ err = f.pacer.Call(func() (bool, error) {
+ resp, err = f.srv.CallJSON(ctx, opts, nil, &result)
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ found, err = callback(&result, err)
+ if found || err != nil {
+ return found, err
+ }
+
+ offset += int64(len(result.Entries))
+ if offset >= result.TotalCount || limit > int64(len(result.Entries)) {
+ break
+ }
+ }
+ return false, nil
+}
+
+// fetchMetadataForPath reads the metadata from the path.
+func (f *Fs) fetchMetadataForPath(ctx context.Context, path string, fields []string) (*api.HiDriveObject, error) {
+ parameters := api.NewQueryParameters()
+ parameters.SetPath(path)
+ parameters.AddFields("", fields...)
+
+ opts := rest.Opts{
+ Method: "GET",
+ Path: "/meta",
+ Parameters: parameters.Values,
+ }
+
+ var result api.HiDriveObject
+ var resp *http.Response
+ var err error
+ err = f.pacer.Call(func() (bool, error) {
+ resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
+ return f.shouldRetry(ctx, resp, err)
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+// copyOrMove copies or moves a directory or file
+// from the source-path to the destination-path.
+//
+// The operation will only be successful
+// if the parent-directory of the destination-path exists.
+//
+// NOTE: Use the explicit methods instead of directly invoking this method.
+// (Those are: copyDirectory, moveDirectory, copyFile, moveFile.)
+func (f *Fs) copyOrMove(ctx context.Context, isDirectory bool, operationType CopyOrMoveOperationType, source string, destination string, onExist OnExistAction) (*api.HiDriveObject, error) {
+ parameters := api.NewQueryParameters()
+ parameters.Set("src", source)
+ parameters.Set("dst", destination)
+ if onExist == AutoNameOnExist ||
+ (onExist == OverwriteOnExist && !isDirectory) {
+ parameters.Set("on_exist", string(onExist))
+ }
+
+ endpoint := "/"
+ if isDirectory {
+ endpoint += "dir"
+ } else {
+ endpoint += "file"
+ }
+ switch operationType {
+ case MoveOriginal:
+ endpoint += "/move"
+ case CopyOriginalPreserveModTime:
+ parameters.Set("preserve_mtime", strconv.FormatBool(true))
+ fallthrough
+ case CopyOriginal:
+ endpoint += "/copy"
+ }
+
+ opts := rest.Opts{
+ Method: "POST",
+ Path: endpoint,
+ Parameters: parameters.Values,
+ }
+
+ var result api.HiDriveObject
+ var resp *http.Response
+ var err error
+ err = f.pacer.Call(func() (bool, error) {
+ resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
+ return f.shouldRetry(ctx, resp, err)
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+// copyDirectory moves the directory at the source-path to the destination-path and
+// returns the resulting api-object if successful.
+//
+// The operation will only be successful
+// if the parent-directory of the destination-path exists.
+func (f *Fs) copyDirectory(ctx context.Context, source string, destination string, onExist OnExistAction) (*api.HiDriveObject, error) {
+ return f.copyOrMove(ctx, true, CopyOriginalPreserveModTime, source, destination, onExist)
+}
+
+// moveDirectory moves the directory at the source-path to the destination-path and
+// returns the resulting api-object if successful.
+//
+// The operation will only be successful
+// if the parent-directory of the destination-path exists.
+func (f *Fs) moveDirectory(ctx context.Context, source string, destination string, onExist OnExistAction) (*api.HiDriveObject, error) {
+ return f.copyOrMove(ctx, true, MoveOriginal, source, destination, onExist)
+}
+
+// copyFile copies the file at the source-path to the destination-path and
+// returns the resulting api-object if successful.
+//
+// The operation will only be successful
+// if the parent-directory of the destination-path exists.
+//
+// NOTE: This operation will expand sparse areas in the content of the source-file
+// to blocks of 0-bytes in the destination-file.
+func (f *Fs) copyFile(ctx context.Context, source string, destination string, onExist OnExistAction) (*api.HiDriveObject, error) {
+ return f.copyOrMove(ctx, false, CopyOriginalPreserveModTime, source, destination, onExist)
+}
+
+// moveFile moves the file at the source-path to the destination-path and
+// returns the resulting api-object if successful.
+//
+// The operation will only be successful
+// if the parent-directory of the destination-path exists.
+//
+// NOTE: This operation may expand sparse areas in the content of the source-file
+// to blocks of 0-bytes in the destination-file.
+func (f *Fs) moveFile(ctx context.Context, source string, destination string, onExist OnExistAction) (*api.HiDriveObject, error) {
+ return f.copyOrMove(ctx, false, MoveOriginal, source, destination, onExist)
+}
+
+// createDirectory creates the directory at the given path and
+// returns the resulting api-object if successful.
+//
+// The directory will only be created if its parent-directory exists.
+// This returns fs.ErrorDirNotFound if the parent-directory is not found.
+// This returns fs.ErrorDirExists if the directory already exists.
+func (f *Fs) createDirectory(ctx context.Context, directory string, onExist OnExistAction) (*api.HiDriveObject, error) {
+ parameters := api.NewQueryParameters()
+ parameters.SetPath(directory)
+ if onExist == AutoNameOnExist {
+ parameters.Set("on_exist", string(onExist))
+ }
+
+ opts := rest.Opts{
+ Method: "POST",
+ Path: "/dir",
+ Parameters: parameters.Values,
+ }
+
+ var result api.HiDriveObject
+ var resp *http.Response
+ var err error
+ err = f.pacer.Call(func() (bool, error) {
+ resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ switch {
+ case err == nil:
+ return &result, nil
+ case isHTTPError(err, 404):
+ return nil, fs.ErrorDirNotFound
+ case isHTTPError(err, 409):
+ return nil, fs.ErrorDirExists
+ }
+ return nil, err
+}
+
+// createDirectories creates the directory at the given path
+// along with any missing parent directories and
+// returns the resulting api-object (of the created directory) if successful.
+//
+// This returns fs.ErrorDirExists if the directory already exists.
+//
+// If an error occurs while the parent directories are being created,
+// any directories already created will NOT be deleted again.
+func (f *Fs) createDirectories(ctx context.Context, directory string, onExist OnExistAction) (*api.HiDriveObject, error) {
+ result, err := f.createDirectory(ctx, directory, onExist)
+ if err == nil {
+ return result, nil
+ }
+ if err != fs.ErrorDirNotFound {
+ return nil, err
+ }
+ parentDirectory := path.Dir(directory)
+ _, err = f.createDirectories(ctx, parentDirectory, onExist)
+ if err != nil && err != fs.ErrorDirExists {
+ return nil, err
+ }
+ // NOTE: Ignoring fs.ErrorDirExists does no harm,
+ // since it does not mean the child directory cannot be created.
+ return f.createDirectory(ctx, directory, onExist)
+}
+
+// deleteDirectory deletes the directory at the given path.
+//
+// If recursive is false, the directory will only be deleted if it is empty.
+// If recursive is true, the directory will be deleted regardless of its content.
+// This returns fs.ErrorDirNotFound if the directory is not found.
+// This returns fs.ErrorDirectoryNotEmpty if the directory is not empty and
+// recursive is false.
+func (f *Fs) deleteDirectory(ctx context.Context, directory string, recursive bool) error {
+ parameters := api.NewQueryParameters()
+ parameters.SetPath(directory)
+ parameters.Set("recursive", strconv.FormatBool(recursive))
+
+ opts := rest.Opts{
+ Method: "DELETE",
+ Path: "/dir",
+ Parameters: parameters.Values,
+ NoResponse: true,
+ }
+
+ var resp *http.Response
+ var err error
+ err = f.pacer.Call(func() (bool, error) {
+ resp, err = f.srv.Call(ctx, &opts)
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ switch {
+ case isHTTPError(err, 404):
+ return fs.ErrorDirNotFound
+ case isHTTPError(err, 409):
+ return fs.ErrorDirectoryNotEmpty
+ }
+ return err
+}
+
+// deleteObject deletes the object/file at the given path.
+//
+// This returns fs.ErrorObjectNotFound if the object is not found.
+func (f *Fs) deleteObject(ctx context.Context, path string) error {
+ parameters := api.NewQueryParameters()
+ parameters.SetPath(path)
+
+ opts := rest.Opts{
+ Method: "DELETE",
+ Path: "/file",
+ Parameters: parameters.Values,
+ NoResponse: true,
+ }
+
+ var resp *http.Response
+ var err error
+ err = f.pacer.Call(func() (bool, error) {
+ resp, err = f.srv.Call(ctx, &opts)
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ if isHTTPError(err, 404) {
+ return fs.ErrorObjectNotFound
+ }
+ return err
+}
+
+// createFile creates a file at the given path
+// with the content of the io.ReadSeeker.
+// This guarantees that existing files will not be overwritten.
+// The maximum size of the content is limited by MaximumUploadBytes.
+// The io.ReadSeeker should be resettable by seeking to its start.
+// If modTime is not the zero time instant,
+// it will be set as the file's modification time after the operation.
+//
+// This returns fs.ErrorDirNotFound
+// if the parent directory of the file is not found.
+// This returns ErrorFileExists if a file already exists at the specified path.
+func (f *Fs) createFile(ctx context.Context, path string, content io.ReadSeeker, modTime time.Time, onExist OnExistAction) (*api.HiDriveObject, error) {
+ parameters := api.NewQueryParameters()
+ parameters.SetFileInDirectory(path)
+ if onExist == AutoNameOnExist {
+ parameters.Set("on_exist", string(onExist))
+ }
+
+ var err error
+ if !modTime.IsZero() {
+ err = parameters.SetTime("mtime", modTime)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ opts := rest.Opts{
+ Method: "POST",
+ Path: "/file",
+ Body: content,
+ ContentType: "application/octet-stream",
+ Parameters: parameters.Values,
+ }
+
+ var result api.HiDriveObject
+ var resp *http.Response
+ err = f.pacer.Call(func() (bool, error) {
+ // Reset the reading index (in case this is a retry).
+ if _, err = content.Seek(0, io.SeekStart); err != nil {
+ return false, err
+ }
+ resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ switch {
+ case err == nil:
+ return &result, nil
+ case isHTTPError(err, 404):
+ return nil, fs.ErrorDirNotFound
+ case isHTTPError(err, 409):
+ return nil, ErrorFileExists
+ }
+ return nil, err
+}
+
+// overwriteFile updates the content of the file at the given path
+// with the content of the io.ReadSeeker.
+// If the file does not exist it will be created.
+// The maximum size of the content is limited by MaximumUploadBytes.
+// The io.ReadSeeker should be resettable by seeking to its start.
+// If modTime is not the zero time instant,
+// it will be set as the file's modification time after the operation.
+//
+// This returns fs.ErrorDirNotFound
+// if the parent directory of the file is not found.
+func (f *Fs) overwriteFile(ctx context.Context, path string, content io.ReadSeeker, modTime time.Time) (*api.HiDriveObject, error) {
+ parameters := api.NewQueryParameters()
+ parameters.SetFileInDirectory(path)
+
+ var err error
+ if !modTime.IsZero() {
+ err = parameters.SetTime("mtime", modTime)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ opts := rest.Opts{
+ Method: "PUT",
+ Path: "/file",
+ Body: content,
+ ContentType: "application/octet-stream",
+ Parameters: parameters.Values,
+ }
+
+ var result api.HiDriveObject
+ var resp *http.Response
+ err = f.pacer.Call(func() (bool, error) {
+ // Reset the reading index (in case this is a retry).
+ if _, err = content.Seek(0, io.SeekStart); err != nil {
+ return false, err
+ }
+ resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ switch {
+ case err == nil:
+ return &result, nil
+ case isHTTPError(err, 404):
+ return nil, fs.ErrorDirNotFound
+ }
+ return nil, err
+}
+
+// uploadFileChunked updates the content of the existing file at the given path
+// with the content of the io.Reader.
+// Returns the position of the last successfully written byte, stopping before the first failed write.
+// If nothing was written this will be 0.
+// Returns the resulting api-object if successful.
+//
+// Replaces the file contents by uploading multiple chunks of the given size in parallel.
+// Therefore this can and be used to upload files of any size efficiently.
+// The number of parallel transfers is limited by transferLimit which should larger than 0.
+// If modTime is not the zero time instant,
+// it will be set as the file's modification time after the operation.
+//
+// NOTE: This method uses updateFileChunked and may create sparse files,
+// if the upload of a chunk fails unexpectedly.
+// See note about sparse files in patchFile.
+// If any of the uploads fail, the process will be aborted and
+// the first error that occurred will be returned.
+// This is not an atomic operation,
+// therefore if the upload fails the file may be partially modified.
+//
+// This returns fs.ErrorObjectNotFound if the object is not found.
+func (f *Fs) uploadFileChunked(ctx context.Context, path string, content io.Reader, modTime time.Time, chunkSize int, transferLimit int64) (okSize uint64, info *api.HiDriveObject, err error) {
+ okSize, err = f.updateFileChunked(ctx, path, content, 0, chunkSize, transferLimit)
+
+ if err == nil {
+ info, err = f.resizeFile(ctx, path, okSize, modTime)
+ }
+ return okSize, info, err
+}
+
+// updateFileChunked updates the content of the existing file at the given path
+// starting at the given offset.
+// Returns the position of the last successfully written byte, stopping before the first failed write.
+// If nothing was written this will be 0.
+//
+// Replaces the file contents starting from the given byte offset
+// with the content of the io.Reader.
+// If the offset is beyond the file end, the file is extended up to the offset.
+//
+// The upload is done multiple chunks of the given size in parallel.
+// Therefore this can and be used to upload files of any size efficiently.
+// The number of parallel transfers is limited by transferLimit which should larger than 0.
+//
+// NOTE: Because it is inefficient to set the modification time with every chunk,
+// setting it to a specific value must be done in a separate request
+// after this operation finishes.
+//
+// NOTE: This method uses patchFile and may create sparse files,
+// especially if the upload of a chunk fails unexpectedly.
+// See note about sparse files in patchFile.
+// If any of the uploads fail, the process will be aborted and
+// the first error that occurred will be returned.
+// This is not an atomic operation,
+// therefore if the upload fails the file may be partially modified.
+//
+// This returns fs.ErrorObjectNotFound if the object is not found.
+func (f *Fs) updateFileChunked(ctx context.Context, path string, content io.Reader, offset uint64, chunkSize int, transferLimit int64) (okSize uint64, err error) {
+ var (
+ okChunksMu sync.Mutex // protects the variables below
+ okChunks []ranges.Range
+ )
+ g, gCtx := errgroup.WithContext(ctx)
+ transferSemaphore := semaphore.NewWeighted(transferLimit)
+
+ var readErr error
+ startMoreTransfers := true
+ zeroTime := time.Time{}
+ for chunk := uint64(0); startMoreTransfers; chunk++ {
+ // Acquire semaphore to limit number of transfers in parallel.
+ readErr = transferSemaphore.Acquire(gCtx, 1)
+ if readErr != nil {
+ break
+ }
+
+ // Read a chunk of data.
+ chunkReader, bytesRead, readErr := readerForChunk(content, chunkSize)
+ if bytesRead < chunkSize {
+ startMoreTransfers = false
+ }
+ if readErr != nil || bytesRead <= 0 {
+ break
+ }
+
+ // Transfer the chunk.
+ chunkOffset := uint64(chunkSize)*chunk + offset
+ g.Go(func() error {
+ // After this upload is done,
+ // signal that another transfer can be started.
+ defer transferSemaphore.Release(1)
+ uploadErr := f.patchFile(gCtx, path, cachedReader(chunkReader), chunkOffset, zeroTime)
+ if uploadErr == nil {
+ // Remember successfully written chunks.
+ okChunksMu.Lock()
+ okChunks = append(okChunks, ranges.Range{Pos: int64(chunkOffset), Size: int64(bytesRead)})
+ okChunksMu.Unlock()
+ fs.Debugf(f, "Done uploading chunk of size %v at offset %v.", bytesRead, chunkOffset)
+ } else {
+ fs.Infof(f, "Error while uploading chunk at offset %v. Error is %v.", chunkOffset, uploadErr)
+ }
+ return uploadErr
+ })
+ }
+
+ if readErr != nil {
+ // Log the error in case it is later ignored because of an upload-error.
+ fs.Infof(f, "Error while reading/preparing to upload a chunk. Error is %v.", readErr)
+ }
+
+ err = g.Wait()
+
+ // Compute the first continuous range of the file content,
+ // which does not contain any failed chunks.
+ // Do not forget to add the file content up to the starting offset,
+ // which is presumed to be already correct.
+ rs := ranges.Ranges{}
+ rs.Insert(ranges.Range{Pos: 0, Size: int64(offset)})
+ for _, chunkRange := range okChunks {
+ rs.Insert(chunkRange)
+ }
+ if len(rs) > 0 && rs[0].Pos == 0 {
+ okSize = uint64(rs[0].Size)
+ }
+
+ if err != nil {
+ return okSize, err
+ }
+ if readErr != nil {
+ return okSize, readErr
+ }
+
+ return okSize, nil
+}
+
+// patchFile updates the content of the existing file at the given path
+// starting at the given offset.
+//
+// Replaces the file contents starting from the given byte offset
+// with the content of the io.ReadSeeker.
+// If the offset is beyond the file end, the file is extended up to the offset.
+// The maximum size of the update is limited by MaximumUploadBytes.
+// The io.ReadSeeker should be resettable by seeking to its start.
+// If modTime is not the zero time instant,
+// it will be set as the file's modification time after the operation.
+//
+// NOTE: By extending the file up to the offset this may create sparse files,
+// which allocate less space on the file system than their apparent size indicates,
+// since holes between data chunks are "real" holes
+// and not regions made up of consecutive 0-bytes.
+// Subsequent operations (such as copying data)
+// usually expand the holes into regions of 0-bytes.
+//
+// This returns fs.ErrorObjectNotFound if the object is not found.
+func (f *Fs) patchFile(ctx context.Context, path string, content io.ReadSeeker, offset uint64, modTime time.Time) error {
+ parameters := api.NewQueryParameters()
+ parameters.SetPath(path)
+ parameters.Set("offset", strconv.FormatUint(offset, 10))
+
+ if !modTime.IsZero() {
+ err := parameters.SetTime("mtime", modTime)
+ if err != nil {
+ return err
+ }
+ }
+
+ opts := rest.Opts{
+ Method: "PATCH",
+ Path: "/file",
+ Body: content,
+ ContentType: "application/octet-stream",
+ Parameters: parameters.Values,
+ NoResponse: true,
+ }
+
+ var resp *http.Response
+ var err error
+ err = f.pacer.Call(func() (bool, error) {
+ // Reset the reading index (in case this is a retry).
+ _, err = content.Seek(0, io.SeekStart)
+ if err != nil {
+ return false, err
+ }
+ resp, err = f.srv.Call(ctx, &opts)
+ if isHTTPError(err, 423) {
+ return true, err
+ }
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ if isHTTPError(err, 404) {
+ return fs.ErrorObjectNotFound
+ }
+ return err
+}
+
+// resizeFile updates the existing file at the given path to be of the given size
+// and returns the resulting api-object if successful.
+//
+// If the given size is smaller than the current filesize,
+// the file is cut/truncated at that position.
+// If the given size is larger, the file is extended up to that position.
+// If modTime is not the zero time instant,
+// it will be set as the file's modification time after the operation.
+//
+// NOTE: By extending the file this may create sparse files,
+// which allocate less space on the file system than their apparent size indicates,
+// since holes between data chunks are "real" holes
+// and not regions made up of consecutive 0-bytes.
+// Subsequent operations (such as copying data)
+// usually expand the holes into regions of 0-bytes.
+//
+// This returns fs.ErrorObjectNotFound if the object is not found.
+func (f *Fs) resizeFile(ctx context.Context, path string, size uint64, modTime time.Time) (*api.HiDriveObject, error) {
+ parameters := api.NewQueryParameters()
+ parameters.SetPath(path)
+ parameters.Set("size", strconv.FormatUint(size, 10))
+
+ if !modTime.IsZero() {
+ err := parameters.SetTime("mtime", modTime)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ opts := rest.Opts{
+ Method: "POST",
+ Path: "/file/truncate",
+ Parameters: parameters.Values,
+ }
+
+ var result api.HiDriveObject
+ var resp *http.Response
+ var err error
+ err = f.pacer.Call(func() (bool, error) {
+ resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
+ return f.shouldRetry(ctx, resp, err)
+ })
+
+ switch {
+ case err == nil:
+ return &result, nil
+ case isHTTPError(err, 404):
+ return nil, fs.ErrorObjectNotFound
+ }
+ return nil, err
+}
+
+// ------------------------------------------------------------
+
+// isHTTPError compares the numerical status code
+// of an api.Error to the given HTTP status.
+//
+// If the given error is not an api.Error or
+// a numerical status code could not be determined, this returns false.
+// Otherwise this returns whether the status code of the error is equal to the given status.
+func isHTTPError(err error, status int64) bool {
+ if apiErr, ok := err.(*api.Error); ok {
+ errStatus, decodeErr := apiErr.Code.Int64()
+ if decodeErr == nil && errStatus == status {
+ return true
+ }
+ }
+ return false
+}
+
+// createHiDriveScopes creates oauth-scopes
+// from the given user-role and access-permissions.
+//
+// If the arguments are empty, they will not be included in the result.
+func createHiDriveScopes(role string, access string) []string {
+ switch {
+ case role != "" && access != "":
+ return []string{access + "," + role}
+ case role != "":
+ return []string{role}
+ case access != "":
+ return []string{access}
+ }
+ return []string{}
+}
+
+// cachedReader returns a version of the reader that caches its contents and
+// can therefore be reset using Seek.
+func cachedReader(reader io.Reader) io.ReadSeeker {
+ bytesReader, ok := reader.(*bytes.Reader)
+ if ok {
+ return bytesReader
+ }
+
+ repeatableReader, ok := reader.(*readers.RepeatableReader)
+ if ok {
+ return repeatableReader
+ }
+
+ return readers.NewRepeatableReader(reader)
+}
+
+// readerForChunk reads a chunk of bytes from reader (after handling any accounting).
+// Returns a new io.Reader (chunkReader) for that chunk
+// and the number of bytes that have been read from reader.
+func readerForChunk(reader io.Reader, length int) (chunkReader io.Reader, bytesRead int, err error) {
+ // Unwrap any accounting from the input if present.
+ reader, wrap := accounting.UnWrap(reader)
+
+ // Read a chunk of data.
+ buffer := make([]byte, length)
+ bytesRead, err = io.ReadFull(reader, buffer)
+ if err == io.EOF || err == io.ErrUnexpectedEOF {
+ err = nil
+ }
+ if err != nil {
+ return nil, bytesRead, err
+ }
+ // Truncate unused capacity.
+ buffer = buffer[:bytesRead]
+
+ // Use wrap to put any accounting back for chunkReader.
+ return wrap(bytes.NewReader(buffer)), bytesRead, nil
+}
diff --git a/backend/hidrive/hidrive.go b/backend/hidrive/hidrive.go
new file mode 100644
index 0000000000000..b0f56844a8767
--- /dev/null
+++ b/backend/hidrive/hidrive.go
@@ -0,0 +1,1002 @@
+// Package hidrive provides an interface to the HiDrive object storage system.
+package hidrive
+
+// FIXME HiDrive only supports file or folder names of 255 characters or less.
+// Operations that create files oder folder with longer names will throw a HTTP error:
+// - 422 Unprocessable Entity
+// A more graceful way for rclone to handle this may be desirable.
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "path"
+ "strconv"
+ "time"
+
+ "github.com/rclone/rclone/lib/encoder"
+
+ "github.com/rclone/rclone/backend/hidrive/api"
+ "github.com/rclone/rclone/backend/hidrive/hidrivehash"
+ "github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fs/config"
+ "github.com/rclone/rclone/fs/config/configmap"
+ "github.com/rclone/rclone/fs/config/configstruct"
+ "github.com/rclone/rclone/fs/config/obscure"
+ "github.com/rclone/rclone/fs/fserrors"
+ "github.com/rclone/rclone/fs/hash"
+ "github.com/rclone/rclone/lib/oauthutil"
+ "github.com/rclone/rclone/lib/pacer"
+ "github.com/rclone/rclone/lib/rest"
+ "golang.org/x/oauth2"
+)
+
+const (
+ rcloneClientID = "6b0258fdda630d34db68a3ce3cbf19ae"
+ rcloneEncryptedClientSecret = "GC7UDZ3Ra4jLcmfQSagKCDJ1JEy-mU6pBBhFrS3tDEHILrK7j3TQHUrglkO5SgZ_"
+ minSleep = 10 * time.Millisecond
+ maxSleep = 2 * time.Second
+ decayConstant = 2 // bigger for slower decay, exponential
+ defaultUploadChunkSize = 48 * fs.Mebi
+ defaultUploadCutoff = 2 * defaultUploadChunkSize
+ defaultUploadConcurrency = 4
+)
+
+// Globals
+var (
+ // Description of how to auth for this app.
+ oauthConfig = &oauth2.Config{
+ Endpoint: oauth2.Endpoint{
+ AuthURL: "https://my.hidrive.com/client/authorize",
+ TokenURL: "https://my.hidrive.com/oauth2/token",
+ },
+ ClientID: rcloneClientID,
+ ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
+ RedirectURL: oauthutil.TitleBarRedirectURL,
+ }
+ // hidrivehashType is the hash.Type for HiDrive hashes.
+ hidrivehashType hash.Type
+)
+
+// Register the backend with Fs.
+func init() {
+ hidrivehashType = hash.RegisterHash("hidrive", "HiDriveHash", 40, hidrivehash.New)
+ fs.Register(&fs.RegInfo{
+ Name: "hidrive",
+ Description: "HiDrive",
+ NewFs: NewFs,
+ Config: func(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
+ // Parse config into Options struct
+ opt := new(Options)
+ err := configstruct.Set(m, opt)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't parse config into struct: %w", err)
+ }
+
+ //fs.Debugf(nil, "hidrive: configuring oauth-token.")
+ oauthConfig.Scopes = createHiDriveScopes(opt.ScopeRole, opt.ScopeAccess)
+ return oauthutil.ConfigOut("", &oauthutil.Options{
+ OAuth2Config: oauthConfig,
+ })
+ },
+ Options: append(oauthutil.SharedOptions, []fs.Option{{
+ Name: "scope_access",
+ Help: "Access permissions that rclone should use when requesting access from HiDrive.",
+ Default: "rw",
+ Examples: []fs.OptionExample{{
+ Value: "rw",
+ Help: "Read and write access to resources.",
+ }, {
+ Value: "ro",
+ Help: "Read-only access to resources.",
+ }},
+ }, {
+ Name: "scope_role",
+ Help: "User-level that rclone should use when requesting access from HiDrive.",
+ Default: "user",
+ Examples: []fs.OptionExample{{
+ Value: "user",
+ Help: `User-level access to management permissions.
+This will be sufficient in most cases.`,
+ }, {
+ Value: "admin",
+ Help: "Extensive access to management permissions.",
+ }, {
+ Value: "owner",
+ Help: "Full access to management permissions.",
+ }},
+ Advanced: true,
+ }, {
+ Name: "root_prefix",
+ Help: `The root/parent folder for all paths.
+
+Fill in to use the specified folder as the parent for all paths given to the remote.
+This way rclone can use any folder as its starting point.`,
+ Default: "/",
+ Examples: []fs.OptionExample{{
+ Value: "/",
+ Help: `The topmost directory accessible by rclone.
+This will be equivalent with "root" if rclone uses a regular HiDrive user account.`,
+ }, {
+ Value: "root",
+ Help: `The topmost directory of the HiDrive user account`,
+ }, {
+ Value: "",
+ Help: `This specifies that there is no root-prefix for your paths.
+When using this you will always need to specify paths to this remote with a valid parent e.g. "remote:/path/to/dir" or "remote:root/path/to/dir".`,
+ }},
+ Advanced: true,
+ }, {
+ Name: "endpoint",
+ Help: `Endpoint for the service.
+
+This is the URL that API-calls will be made to.`,
+ Default: "https://api.hidrive.strato.com/2.1",
+ Advanced: true,
+ }, {
+ Name: "disable_fetching_member_count",
+ Help: `Do not fetch number of objects in directories unless it is absolutely necessary.
+
+Requests may be faster if the number of objects in subdirectories is not fetched.`,
+ Default: false,
+ Advanced: true,
+ }, {
+ Name: "chunk_size",
+ Help: fmt.Sprintf(`Chunksize for chunked uploads.
+
+Any files larger than the configured cutoff (or files of unknown size) will be uploaded in chunks of this size.
+
+The upper limit for this is %v bytes (about %v).
+That is the maximum amount of bytes a single upload-operation will support.
+Setting this above the upper limit or to a negative value will cause uploads to fail.
+
+Setting this to larger values may increase the upload speed at the cost of using more memory.
+It can be set to smaller values smaller to save on memory.`, MaximumUploadBytes, fs.SizeSuffix(MaximumUploadBytes)),
+ Default: defaultUploadChunkSize,
+ Advanced: true,
+ }, {
+ Name: "upload_cutoff",
+ Help: fmt.Sprintf(`Cutoff/Threshold for chunked uploads.
+
+Any files larger than this will be uploaded in chunks of the configured chunksize.
+
+The upper limit for this is %v bytes (about %v).
+That is the maximum amount of bytes a single upload-operation will support.
+Setting this above the upper limit will cause uploads to fail.`, MaximumUploadBytes, fs.SizeSuffix(MaximumUploadBytes)),
+ Default: defaultUploadCutoff,
+ Advanced: true,
+ }, {
+ Name: "upload_concurrency",
+ Help: `Concurrency for chunked uploads.
+
+This is the upper limit for how many transfers for the same file are running concurrently.
+Setting this above to a value smaller than 1 will cause uploads to deadlock.
+
+If you are uploading small numbers of large files over high-speed links
+and these uploads do not fully utilize your bandwidth, then increasing
+this may help to speed up the transfers.`,
+ Default: defaultUploadConcurrency,
+ Advanced: true,
+ }, {
+ Name: config.ConfigEncoding,
+ Help: config.ConfigEncodingHelp,
+ Advanced: true,
+ // HiDrive only supports file or folder names of 255 characters or less.
+ // Names containing "/" are not supported.
+ // The special names "." and ".." are not supported.
+ Default: (encoder.EncodeZero |
+ encoder.EncodeSlash |
+ encoder.EncodeDot),
+ }}...),
+ })
+}
+
+// Options defines the configuration for this backend.
+type Options struct {
+ EndpointAPI string `config:"endpoint"`
+ OptionalMemberCountDisabled bool `config:"disable_fetching_member_count"`
+ UploadChunkSize fs.SizeSuffix `config:"chunk_size"`
+ UploadCutoff fs.SizeSuffix `config:"upload_cutoff"`
+ UploadConcurrency int64 `config:"upload_concurrency"`
+ Enc encoder.MultiEncoder `config:"encoding"`
+ RootPrefix string `config:"root_prefix"`
+ ScopeAccess string `config:"scope_access"`
+ ScopeRole string `config:"scope_role"`
+}
+
+// Fs represents a remote hidrive.
+type Fs struct {
+ name string // name of this remote
+ root string // the path we are working on
+ opt Options // parsed options
+ features *fs.Features // optional features
+ srv *rest.Client // the connection to the server
+ pacer *fs.Pacer // pacer for API calls
+ // retryOnce is NOT intended as a pacer for API calls.
+ // The intended use case is to repeat an action that failed because
+ // some preconditions were not previously fulfilled.
+ // Code using this should then establish these preconditions
+ // and let the pacer retry the operation.
+ retryOnce *pacer.Pacer // pacer with no delays to retry certain operations once
+ tokenRenewer *oauthutil.Renew // renew the token on expiry
+}
+
+// Object describes a hidrive object.
+//
+// Will definitely have the remote-path but may lack meta-information.
+type Object struct {
+ fs *Fs // what this object is part of
+ remote string // The remote path
+ hasMetadata bool // whether info below has been set
+ size int64 // size of the object
+ modTime time.Time // modification time of the object
+ id string // ID of the object
+ hash string // content-hash of the object
+}
+
+// ------------------------------------------------------------
+
+// Name returns the name of the remote (as passed into NewFs).
+func (f *Fs) Name() string {
+ return f.name
+}
+
+// Root returns the name of the remote (as passed into NewFs).
+func (f *Fs) Root() string {
+ return f.root
+}
+
+// String returns a string-representation of this Fs.
+func (f *Fs) String() string {
+ return fmt.Sprintf("HiDrive root '%s'", f.root)
+}
+
+// Precision returns the precision of this Fs.
+func (f *Fs) Precision() time.Duration {
+ return time.Second
+}
+
+// Hashes returns the supported hash sets.
+func (f *Fs) Hashes() hash.Set {
+ return hash.Set(hidrivehashType)
+}
+
+// Features returns the optional features of this Fs.
+func (f *Fs) Features() *fs.Features {
+ return f.features
+}
+
+// errorHandler parses a non 2xx error response into an error.
+func errorHandler(resp *http.Response) error {
+ // Decode error response.
+ errResponse := new(api.Error)
+ err := rest.DecodeJSON(resp, &errResponse)
+ if err != nil {
+ fs.Debugf(nil, "Couldn't decode error response: %v", err)
+ }
+ _, err = errResponse.Code.Int64()
+ if err != nil {
+ errResponse.Code = json.Number(strconv.Itoa(resp.StatusCode))
+ }
+ return errResponse
+}
+
+// NewFs creates a new file system from the path.
+func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
+ //fs.Debugf(nil, "hidrive: creating new Fs.")
+ // Parse config into Options struct.
+ opt := new(Options)
+ err := configstruct.Set(m, opt)
+ if err != nil {
+ return nil, err
+ }
+
+ // Clean root-prefix and root-path.
+ // NOTE: With the default-encoding "." and ".." will be encoded,
+ // but with custom encodings without encoder.EncodeDot
+ // "." and ".." will be interpreted as paths.
+ if opt.RootPrefix != "" {
+ opt.RootPrefix = path.Clean(opt.Enc.FromStandardPath(opt.RootPrefix))
+ }
+ root = path.Clean(opt.Enc.FromStandardPath(root))
+
+ client, ts, err := oauthutil.NewClient(ctx, name, m, oauthConfig)
+ if err != nil {
+ return nil, fmt.Errorf("failed to configure HiDrive: %w", err)
+ }
+
+ f := &Fs{
+ name: name,
+ root: root,
+ opt: *opt,
+ srv: rest.NewClient(client).SetRoot(opt.EndpointAPI),
+ pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
+ retryOnce: pacer.New(pacer.RetriesOption(2), pacer.MaxConnectionsOption(-1), pacer.CalculatorOption(&pacer.ZeroDelayCalculator{})),
+ }
+ f.features = (&fs.Features{
+ CanHaveEmptyDirectories: true,
+ }).Fill(ctx, f)
+ f.srv.SetErrorHandler(errorHandler)
+
+ if ts != nil {
+ transaction := func() error {
+ resolvedRoot := f.resolvePath("")
+ _, err := f.fetchMetadataForPath(ctx, resolvedRoot, api.HiDriveObjectNoMetadataFields)
+ return err
+ }
+ f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, transaction)
+ }
+
+ // Do not allow the root-prefix to be non-existent nor a directory,
+ // but it can be empty.
+ if f.opt.RootPrefix != "" {
+ item, err := f.fetchMetadataForPath(ctx, f.opt.RootPrefix, api.HiDriveObjectNoMetadataFields)
+ if err != nil {
+ return nil, fmt.Errorf("could not access root-prefix: %w", err)
+ }
+ if item.Type != api.HiDriveObjectTypeDirectory {
+ return nil, errors.New("The root-prefix needs to point to a valid directory or be empty")
+ }
+ }
+
+ resolvedRoot := f.resolvePath("")
+ item, err := f.fetchMetadataForPath(ctx, resolvedRoot, api.HiDriveObjectNoMetadataFields)
+ if err != nil {
+ if isHTTPError(err, 404) {
+ // NOTE: NewFs needs to work with paths that do not exist,
+ // in case they will be created later (see mkdir).
+ return f, nil
+ }
+ return nil, fmt.Errorf("could not access root-path: %w", err)
+ }
+ if item.Type != api.HiDriveObjectTypeDirectory {
+ fs.Debugf(f, "The root is not a directory. Setting its parent-directory as the new root.")
+ // NOTE: There is no need to check
+ // if the parent-directory is inside the root-prefix:
+ // If the parent-directory was outside,
+ // then the resolved path would be the root-prefix,
+ // therefore the root-prefix would point to a file,
+ // which has already been checked for.
+ // In case the root-prefix is empty, this needs not be checked,
+ // because top-level files cannot exist.
+ f.root = path.Dir(f.root)
+ return f, fs.ErrorIsFile
+ }
+
+ return f, nil
+}
+
+// newObject constructs an Object by calling the given function metaFiller
+// on an Object with no metadata.
+//
+// metaFiller should set the metadata of the object or
+// return an appropriate error.
+func (f *Fs) newObject(remote string, metaFiller func(*Object) error) (fs.Object, error) {
+ o := &Object{
+ fs: f,
+ remote: remote,
+ }
+ var err error
+ if metaFiller != nil {
+ err = metaFiller(o)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return o, nil
+}
+
+// newObjectFromHiDriveObject constructs an Object from the given api.HiDriveObject.
+func (f *Fs) newObjectFromHiDriveObject(remote string, info *api.HiDriveObject) (fs.Object, error) {
+ metaFiller := func(o *Object) error {
+ return o.setMetadata(info)
+ }
+ return f.newObject(remote, metaFiller)
+}
+
+// NewObject finds the Object at remote.
+//
+// If remote points to a directory then it returns fs.ErrorIsDir.
+// If it can not be found it returns the error fs.ErrorObjectNotFound.
+func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
+ //fs.Debugf(f, "executing NewObject(%s).", remote)
+ metaFiller := func(o *Object) error {
+ return o.readMetadata(ctx)
+ }
+ return f.newObject(remote, metaFiller)
+}
+
+// List the objects and directories in dir into entries.
+// The entries can be returned in any order,
+// but should be for a complete directory.
+//
+// dir should be "" to list the root, and should not have trailing slashes.
+//
+// This returns fs.ErrorDirNotFound if the directory is not found.
+func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
+ //fs.Debugf(f, "executing List(%s).", dir)
+ var iErr error
+ addEntry := func(info *api.HiDriveObject) bool {
+ fs.Debugf(f, "found directory-element with name %s", info.Name)
+ remote := path.Join(dir, info.Name)
+ if info.Type == api.HiDriveObjectTypeDirectory {
+ d := fs.NewDir(remote, info.ModTime())
+ d.SetID(info.ID)
+ d.SetSize(info.Size)
+ d.SetItems(info.MemberCount)
+ entries = append(entries, d)
+ } else if info.Type == api.HiDriveObjectTypeFile {
+ o, err := f.newObjectFromHiDriveObject(remote, info)
+ if err != nil {
+ iErr = err
+ return true
+ }
+ entries = append(entries, o)
+ }
+ return false
+ }
+
+ var fields []string
+ if f.opt.OptionalMemberCountDisabled {
+ fields = api.HiDriveObjectWithMetadataFields
+ } else {
+ fields = api.HiDriveObjectWithDirectoryMetadataFields
+ }
+ resolvedDir := f.resolvePath(dir)
+ _, err = f.iterateOverDirectory(ctx, resolvedDir, AllMembers, addEntry, fields, Unsorted)
+
+ if err != nil {
+ if isHTTPError(err, 404) {
+ return nil, fs.ErrorDirNotFound
+ }
+ return nil, err
+ }
+ if iErr != nil {
+ return nil, iErr
+ }
+ return entries, nil
+}
+
+// Put the contents of the io.Reader into the remote path
+// with the modTime given of the given size.
+// The existing or new object is returned.
+//
+// A new object may have been created or
+// an existing one accessed even if an error is returned,
+// in which case both the object and the error will be returned.
+func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
+ remote := src.Remote()
+ //fs.Debugf(f, "executing Put(%s, %v).", remote, options)
+
+ existingObj, err := f.NewObject(ctx, remote)
+ switch err {
+ case nil:
+ return existingObj, existingObj.Update(ctx, in, src, options...)
+ case fs.ErrorObjectNotFound:
+ // Object was not found, so create a new one.
+ return f.PutUnchecked(ctx, in, src, options...)
+ }
+ return nil, err
+}
+
+// PutStream uploads the contents of the io.Reader to the remote path
+// with the modTime given of indeterminate size.
+// The existing or new object is returned.
+//
+// A new object may have been created or
+// an existing one accessed even if an error is returned,
+// in which case both the object and the error will be returned.
+func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
+ //fs.Debugf(f, "executing PutStream(%s, %v).", src.Remote(), options)
+
+ return f.Put(ctx, in, src, options...)
+}
+
+// PutUnchecked the contents of the io.Reader into the remote path
+// with the modTime given of the given size.
+// This guarantees that existing objects will not be overwritten.
+// The new object is returned.
+//
+// This will produce an error if an object already exists at that path.
+//
+// In case the upload fails and an object has been created,
+// this will try to delete the object at that path.
+// In case the failed upload could not be deleted,
+// both the object and the (upload-)error will be returned.
+func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
+ remote := src.Remote()
+ modTime := src.ModTime(ctx)
+ //fs.Debugf(f, "executing PutUnchecked(%s, %v).", remote, options)
+ resolvedPath := f.resolvePath(remote)
+
+ // NOTE: The file creation operation is a single atomic operation.
+ // Thus uploading as much content as is reasonable
+ // (i.e. everything up to the cutoff) in the first request,
+ // avoids files being created on upload failure for small files.
+ // (As opposed to creating an empty file and then uploading the content.)
+ tmpReader, bytesRead, err := readerForChunk(in, int(f.opt.UploadCutoff))
+ cutoffReader := cachedReader(tmpReader)
+ if err != nil {
+ return nil, err
+ }
+
+ var info *api.HiDriveObject
+ err = f.retryOnce.Call(func() (bool, error) {
+ var createErr error
+ // Reset the reading index (in case this is a retry).
+ if _, createErr = cutoffReader.Seek(0, io.SeekStart); createErr != nil {
+ return false, createErr
+ }
+ info, createErr = f.createFile(ctx, resolvedPath, cutoffReader, modTime, IgnoreOnExist)
+
+ if createErr == fs.ErrorDirNotFound {
+ // Create the parent-directory for the object and repeat request.
+ _, parentErr := f.createDirectories(ctx, path.Dir(resolvedPath), IgnoreOnExist)
+ if parentErr != nil && parentErr != fs.ErrorDirExists {
+ fs.Errorf(f, "Tried to create parent-directory for '%s', but failed.", resolvedPath)
+ return false, parentErr
+ }
+ return true, createErr
+ }
+ return false, createErr
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ o, err := f.newObjectFromHiDriveObject(remote, info)
+
+ if err != nil {
+ return nil, err
+ }
+
+ if fs.SizeSuffix(bytesRead) < f.opt.UploadCutoff {
+ return o, nil
+ }
+ // If there is more left to write, o.Update needs to skip ahead.
+ // Use a fs.SeekOption with the current offset to do this.
+ options = append(options, &fs.SeekOption{Offset: int64(bytesRead)})
+ err = o.Update(ctx, in, src, options...)
+
+ if err == nil {
+ return o, nil
+ }
+
+ // Try to remove object at path after the its content could not be uploaded.
+ deleteErr := f.pacer.Call(func() (bool, error) {
+ deleteErr := o.Remove(ctx)
+ return deleteErr == fs.ErrorObjectNotFound, deleteErr
+ })
+
+ if deleteErr == nil {
+ return nil, err
+ }
+
+ fs.Errorf(f, "Tried to delete failed upload at path '%s', but failed: %v", resolvedPath, deleteErr)
+ return o, err
+}
+
+// Mkdir creates the directory if it does not exist.
+//
+// This will create any missing parent directories.
+//
+// NOTE: If an error occurs while the parent directories are being created,
+// any directories already created will NOT be deleted again.
+func (f *Fs) Mkdir(ctx context.Context, dir string) error {
+ //fs.Debugf(f, "executing Mkdir(%s).", dir)
+ resolvedDir := f.resolvePath(dir)
+ _, err := f.createDirectories(ctx, resolvedDir, IgnoreOnExist)
+
+ if err == fs.ErrorDirExists {
+ // NOTE: The conflict is caused by the directory already existing,
+ // which should be ignored here.
+ return nil
+ }
+
+ return err
+}
+
+// Rmdir removes the directory if empty.
+//
+// This returns fs.ErrorDirNotFound if the directory is not found.
+// This returns fs.ErrorDirectoryNotEmpty if the directory is not empty.
+func (f *Fs) Rmdir(ctx context.Context, dir string) error {
+ //fs.Debugf(f, "executing Rmdir(%s).", dir)
+ resolvedDir := f.resolvePath(dir)
+ return f.deleteDirectory(ctx, resolvedDir, false)
+}
+
+// Purge removes the directory and all of its contents.
+//
+// This returns fs.ErrorDirectoryNotEmpty if the directory is not empty.
+func (f *Fs) Purge(ctx context.Context, dir string) error {
+ //fs.Debugf(f, "executing Purge(%s).", dir)
+ resolvedDir := f.resolvePath(dir)
+ return f.deleteDirectory(ctx, resolvedDir, true)
+}
+
+// shouldRetryAndCreateParents returns a boolean as to whether the operation
+// should be retried after the parent-directories of the destination have been created.
+// If so, it will create the parent-directories.
+//
+// If any errors arrise while finding the source or
+// creating the parent-directory those will be returned.
+// Otherwise returns the originalError.
+func (f *Fs) shouldRetryAndCreateParents(ctx context.Context, destinationPath string, sourcePath string, originalError error) (bool, error) {
+ if fserrors.ContextError(ctx, &originalError) {
+ return false, originalError
+ }
+ if isHTTPError(originalError, 404) {
+ // Check if source is missing.
+ _, srcErr := f.fetchMetadataForPath(ctx, sourcePath, api.HiDriveObjectNoMetadataFields)
+ if srcErr != nil {
+ return false, srcErr
+ }
+ // Source exists, so the parent of the destination must have been missing.
+ // Create the parent-directory and repeat request.
+ _, parentErr := f.createDirectories(ctx, path.Dir(destinationPath), IgnoreOnExist)
+ if parentErr != nil && parentErr != fs.ErrorDirExists {
+ fs.Errorf(f, "Tried to create parent-directory for '%s', but failed.", destinationPath)
+ return false, parentErr
+ }
+ return true, originalError
+ }
+ return false, originalError
+}
+
+// Copy src to this remote using server-side copy operations.
+//
+// It returns the destination Object and a possible error.
+//
+// This returns fs.ErrorCantCopy if the operation cannot be performed.
+//
+// NOTE: If an error occurs when copying the Object,
+// any parent-directories already created will NOT be deleted again.
+//
+// NOTE: This operation will expand sparse areas in the content of the source-Object
+// to blocks of 0-bytes in the destination-Object.
+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 copy - not same remote type")
+ return nil, fs.ErrorCantCopy
+ }
+ // Get the absolute path to the source.
+ srcPath := srcObj.fs.resolvePath(srcObj.Remote())
+ //fs.Debugf(f, "executing Copy(%s, %s).", srcPath, remote)
+ dstPath := f.resolvePath(remote)
+
+ var info *api.HiDriveObject
+ err := f.retryOnce.Call(func() (bool, error) {
+ var copyErr error
+ info, copyErr = f.copyFile(ctx, srcPath, dstPath, OverwriteOnExist)
+ return f.shouldRetryAndCreateParents(ctx, dstPath, srcPath, copyErr)
+ })
+
+ if err != nil {
+ return nil, err
+ }
+ dstObj, err := f.newObjectFromHiDriveObject(remote, info)
+ if err != nil {
+ return nil, err
+ }
+ return dstObj, nil
+}
+
+// Move src to this remote using server-side move operations.
+//
+// It returns the destination Object and a possible error.
+//
+// This returns fs.ErrorCantMove if the operation cannot be performed.
+//
+// NOTE: If an error occurs when moving the Object,
+// any parent-directories already created will NOT be deleted again.
+//
+// NOTE: This operation will expand sparse areas in the content of the source-Object
+// to blocks of 0-bytes in the destination-Object.
+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
+ }
+ // Get the absolute path to the source.
+ srcPath := srcObj.fs.resolvePath(srcObj.Remote())
+ //fs.Debugf(f, "executing Move(%s, %s).", srcPath, remote)
+ dstPath := f.resolvePath(remote)
+
+ var info *api.HiDriveObject
+ err := f.retryOnce.Call(func() (bool, error) {
+ var moveErr error
+ info, moveErr = f.moveFile(ctx, srcPath, dstPath, OverwriteOnExist)
+ return f.shouldRetryAndCreateParents(ctx, dstPath, srcPath, moveErr)
+ })
+
+ if err != nil {
+ return nil, err
+ }
+ dstObj, err := f.newObjectFromHiDriveObject(remote, info)
+ if err != nil {
+ return nil, err
+ }
+ return dstObj, nil
+
+}
+
+// DirMove moves from src at srcRemote to this remote at dstRemote
+// using server-side move operations.
+//
+// This returns fs.ErrorCantCopy if the operation cannot be performed.
+// This returns fs.ErrorDirExists if the destination already exists.
+//
+// NOTE: If an error occurs when moving the directory,
+// any parent-directories already created will NOT be deleted again.
+func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
+ srcFs, ok := src.(*Fs)
+ if !ok {
+ fs.Debugf(srcFs, "Can't move directory - not same remote type")
+ return fs.ErrorCantDirMove
+ }
+
+ // Get the absolute path to the source.
+ srcPath := srcFs.resolvePath(srcRemote)
+ //fs.Debugf(f, "executing DirMove(%s, %s).", srcPath, dstRemote)
+ dstPath := f.resolvePath(dstRemote)
+
+ err := f.retryOnce.Call(func() (bool, error) {
+ var moveErr error
+ _, moveErr = f.moveDirectory(ctx, srcPath, dstPath, IgnoreOnExist)
+ return f.shouldRetryAndCreateParents(ctx, dstPath, srcPath, moveErr)
+ })
+
+ if err != nil {
+ if isHTTPError(err, 409) {
+ return fs.ErrorDirExists
+ }
+ return err
+ }
+ return nil
+}
+
+// ------------------------------------------------------------
+
+// Fs returns the parent Fs.
+func (o *Object) Fs() fs.Info {
+ return o.fs
+}
+
+// String returns a string-representation of this Object.
+func (o *Object) String() string {
+ if o == nil {
+ return ""
+ }
+ return o.remote
+}
+
+// Remote returns the remote path.
+func (o *Object) Remote() string {
+ return o.remote
+}
+
+// ID returns the ID of the Object if known, or "" if not.
+func (o *Object) ID() string {
+ err := o.readMetadata(context.TODO())
+ if err != nil {
+ fs.Logf(o, "Failed to read metadata: %v", err)
+ return ""
+ }
+ return o.id
+}
+
+// Hash returns the selected checksum of the file.
+// If no checksum is available it returns "".
+func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
+ err := o.readMetadata(ctx)
+ if err != nil {
+ return "", fmt.Errorf("failed to read hash from metadata: %w", err)
+ }
+ switch t {
+ case hidrivehashType:
+ return o.hash, nil
+ default:
+ return "", hash.ErrUnsupported
+ }
+}
+
+// Size returns the size of an object in bytes.
+func (o *Object) Size() int64 {
+ err := o.readMetadata(context.TODO())
+ if err != nil {
+ fs.Logf(o, "Failed to read metadata: %v", err)
+ return -1
+ }
+ return o.size
+}
+
+// setMetadata sets the metadata from info.
+func (o *Object) setMetadata(info *api.HiDriveObject) error {
+ if info.Type == api.HiDriveObjectTypeDirectory {
+ return fs.ErrorIsDir
+ }
+ if info.Type != api.HiDriveObjectTypeFile {
+ return fmt.Errorf("%q is %q: %w", o.remote, info.Type, fs.ErrorNotAFile)
+ }
+ o.hasMetadata = true
+ o.size = info.Size
+ o.modTime = info.ModTime()
+ o.id = info.ID
+ o.hash = info.ContentHash
+ return nil
+}
+
+// readMetadata fetches the metadata if it has not already been fetched.
+func (o *Object) readMetadata(ctx context.Context) error {
+ if o.hasMetadata {
+ return nil
+ }
+ resolvedPath := o.fs.resolvePath(o.remote)
+ info, err := o.fs.fetchMetadataForPath(ctx, resolvedPath, api.HiDriveObjectWithMetadataFields)
+ if err != nil {
+ if isHTTPError(err, 404) {
+ return fs.ErrorObjectNotFound
+ }
+ return err
+ }
+ return o.setMetadata(info)
+}
+
+// ModTime returns the modification time of the object.
+func (o *Object) ModTime(ctx context.Context) time.Time {
+ err := o.readMetadata(ctx)
+ if err != nil {
+ fs.Logf(o, "Failed to read metadata: %v", err)
+ return time.Now()
+ }
+ return o.modTime
+}
+
+// SetModTime sets the metadata on the object to set the modification date.
+func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
+ parameters := api.NewQueryParameters()
+ resolvedPath := o.fs.resolvePath(o.remote)
+ parameters.SetPath(resolvedPath)
+ err := parameters.SetTime("mtime", modTime)
+
+ if err != nil {
+ return err
+ }
+
+ opts := rest.Opts{
+ Method: "PATCH",
+ Path: "/meta",
+ Parameters: parameters.Values,
+ NoResponse: true,
+ }
+
+ var resp *http.Response
+ err = o.fs.pacer.Call(func() (bool, error) {
+ resp, err = o.fs.srv.Call(ctx, &opts)
+ return o.fs.shouldRetry(ctx, resp, err)
+ })
+ if err != nil {
+ return err
+ }
+ o.modTime = modTime
+ return nil
+}
+
+// Storable says whether this object can be stored.
+func (o *Object) Storable() bool {
+ return true
+}
+
+// Open an object for reading.
+func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
+ parameters := api.NewQueryParameters()
+ resolvedPath := o.fs.resolvePath(o.remote)
+ parameters.SetPath(resolvedPath)
+
+ fs.FixRangeOption(options, o.Size())
+ opts := rest.Opts{
+ Method: "GET",
+ Path: "/file",
+ Parameters: parameters.Values,
+ Options: options,
+ }
+ var resp *http.Response
+ var err error
+ err = o.fs.pacer.Call(func() (bool, error) {
+ resp, err = o.fs.srv.Call(ctx, &opts)
+ return o.fs.shouldRetry(ctx, resp, err)
+ })
+ if err != nil {
+ return nil, err
+ }
+ return resp.Body, err
+}
+
+// Update the existing object
+// with the contents of the io.Reader, modTime and size.
+//
+// For unknown-sized contents (indicated by src.Size() == -1)
+// this will try to properly upload it in multiple chunks.
+func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
+ //fs.Debugf(o.fs, "executing Update(%s, %v).", o.remote, options)
+ modTime := src.ModTime(ctx)
+ resolvedPath := o.fs.resolvePath(o.remote)
+
+ if o.fs.tokenRenewer != nil {
+ o.fs.tokenRenewer.Start()
+ defer o.fs.tokenRenewer.Stop()
+ }
+
+ // PutUnchecked can pass a valid SeekOption to skip ahead.
+ var offset uint64
+ for _, option := range options {
+ if seekoption, ok := option.(*fs.SeekOption); ok {
+ offset = uint64(seekoption.Offset)
+ break
+ }
+ }
+
+ var info *api.HiDriveObject
+ var err, metaErr error
+ if offset > 0 || src.Size() == -1 || src.Size() >= int64(o.fs.opt.UploadCutoff) {
+ fs.Debugf(o.fs, "Uploading with chunks of size %v and %v transfers in parallel at path '%s'.", int(o.fs.opt.UploadChunkSize), o.fs.opt.UploadConcurrency, resolvedPath)
+ // NOTE: o.fs.opt.UploadChunkSize should always
+ // be between 0 and MaximumUploadBytes,
+ // so the conversion to an int does not cause problems for valid inputs.
+ if offset > 0 {
+ // NOTE: The offset is only set
+ // when the file was newly created,
+ // therefore the file does not need truncating.
+ _, err = o.fs.updateFileChunked(ctx, resolvedPath, in, offset, int(o.fs.opt.UploadChunkSize), o.fs.opt.UploadConcurrency)
+ if err == nil {
+ err = o.SetModTime(ctx, modTime)
+ }
+ } else {
+ _, _, err = o.fs.uploadFileChunked(ctx, resolvedPath, in, modTime, int(o.fs.opt.UploadChunkSize), o.fs.opt.UploadConcurrency)
+ }
+ // Try to check if object was updated, eitherway.
+ // Metadata should be updated even if the upload fails.
+ info, metaErr = o.fs.fetchMetadataForPath(ctx, resolvedPath, api.HiDriveObjectWithMetadataFields)
+ } else {
+ info, err = o.fs.overwriteFile(ctx, resolvedPath, cachedReader(in), modTime)
+ metaErr = err
+ }
+
+ // Update metadata of this object,
+ // if there was no error with getting the metadata.
+ if metaErr == nil {
+ metaErr = o.setMetadata(info)
+ }
+
+ // Errors with the upload-process are more relevant, return those first.
+ if err != nil {
+ return err
+ }
+ return metaErr
+}
+
+// Remove an object.
+func (o *Object) Remove(ctx context.Context) error {
+ resolvedPath := o.fs.resolvePath(o.remote)
+ return o.fs.deleteObject(ctx, resolvedPath)
+}
+
+// Check the interfaces are satisfied.
+var (
+ _ fs.Fs = (*Fs)(nil)
+ _ fs.Purger = (*Fs)(nil)
+ _ fs.PutStreamer = (*Fs)(nil)
+ _ fs.PutUncheckeder = (*Fs)(nil)
+ _ fs.Copier = (*Fs)(nil)
+ _ fs.Mover = (*Fs)(nil)
+ _ fs.DirMover = (*Fs)(nil)
+ _ fs.Object = (*Object)(nil)
+ _ fs.IDer = (*Object)(nil)
+)
diff --git a/backend/hidrive/hidrive_test.go b/backend/hidrive/hidrive_test.go
new file mode 100644
index 0000000000000..30e1ef2f1c2be
--- /dev/null
+++ b/backend/hidrive/hidrive_test.go
@@ -0,0 +1,45 @@
+// Test HiDrive filesystem interface
+package hidrive
+
+import (
+ "testing"
+
+ "github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fstest/fstests"
+)
+
+// TestIntegration runs integration tests against the remote.
+func TestIntegration(t *testing.T) {
+ name := "TestHiDrive"
+ fstests.Run(t, &fstests.Opt{
+ RemoteName: name + ":",
+ NilObject: (*Object)(nil),
+ ChunkedUpload: fstests.ChunkedUploadConfig{
+ MinChunkSize: 1,
+ MaxChunkSize: MaximumUploadBytes,
+ CeilChunkSize: nil,
+ NeedMultipleChunks: false,
+ },
+ })
+}
+
+// Change the configured UploadChunkSize.
+// Will only be called while no transfer is in progress.
+func (f *Fs) SetUploadChunkSize(chunksize fs.SizeSuffix) (fs.SizeSuffix, error) {
+ var old fs.SizeSuffix
+ old, f.opt.UploadChunkSize = f.opt.UploadChunkSize, chunksize
+ return old, nil
+}
+
+// Change the configured UploadCutoff.
+// Will only be called while no transfer is in progress.
+func (f *Fs) SetUploadCutoff(cutoff fs.SizeSuffix) (fs.SizeSuffix, error) {
+ var old fs.SizeSuffix
+ old, f.opt.UploadCutoff = f.opt.UploadCutoff, cutoff
+ return old, nil
+}
+
+var (
+ _ fstests.SetUploadChunkSizer = (*Fs)(nil)
+ _ fstests.SetUploadCutoffer = (*Fs)(nil)
+)
diff --git a/backend/hidrive/hidrivehash/hidrivehash.go b/backend/hidrive/hidrivehash/hidrivehash.go
new file mode 100644
index 0000000000000..092663d4229e5
--- /dev/null
+++ b/backend/hidrive/hidrivehash/hidrivehash.go
@@ -0,0 +1,410 @@
+// Package hidrivehash implements the HiDrive hashing algorithm which combines SHA-1 hashes hierarchically to a single top-level hash.
+//
+// Note: This implementation does not grant access to any partial hashes generated.
+//
+// See: https://developer.hidrive.com/wp-content/uploads/2021/07/HiDrive_Synchronization-v3.3-rev28.pdf
+// (link to newest version: https://static.hidrive.com/dev/0001)
+package hidrivehash
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "encoding"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "hash"
+ "io"
+
+ "github.com/rclone/rclone/backend/hidrive/hidrivehash/internal"
+)
+
+const (
+ // BlockSize of the checksum in bytes.
+ BlockSize = 4096
+ // Size of the checksum in bytes.
+ Size = sha1.Size
+ // sumsPerLevel is the number of checksums
+ sumsPerLevel = 256
+)
+
+var (
+ // zeroSum is a special hash consisting of 20 null-bytes.
+ // This will be the hash of any empty file (or ones containing only null-bytes).
+ zeroSum = [Size]byte{}
+ // ErrorInvalidEncoding is returned when a hash should be decoded from a binary form that is invalid.
+ ErrorInvalidEncoding = errors.New("encoded binary form is invalid for this hash")
+ // ErrorHashFull is returned when a hash reached its capacity and cannot accept any more input.
+ ErrorHashFull = errors.New("hash reached its capacity")
+)
+
+// writeByBlock writes len(p) bytes from p to the io.Writer in blocks of size blockSize.
+// It returns the number of bytes written from p (0 <= n <= len(p))
+// and any error encountered that caused the write to stop early.
+//
+// A pointer bytesInBlock to a counter needs to be supplied,
+// that is used to keep track how many bytes have been written to the writer already.
+// A pointer onlyNullBytesInBlock to a boolean needs to be supplied,
+// that is used to keep track whether the block so far only consists of null-bytes.
+// The callback onBlockWritten is called whenever a full block has been written to the writer
+// and is given as input the number of bytes that still need to be written.
+func writeByBlock(p []byte, writer io.Writer, blockSize uint32, bytesInBlock *uint32, onlyNullBytesInBlock *bool, onBlockWritten func(remaining int) error) (n int, err error) {
+ total := len(p)
+ nullBytes := make([]byte, blockSize)
+ for len(p) > 0 {
+ toWrite := int(blockSize - *bytesInBlock)
+ if toWrite > len(p) {
+ toWrite = len(p)
+ }
+ c, err := writer.Write(p[:toWrite])
+ *bytesInBlock += uint32(c)
+ *onlyNullBytesInBlock = *onlyNullBytesInBlock && bytes.Equal(nullBytes[:toWrite], p[:toWrite])
+ // Discard data written through a reslice
+ p = p[c:]
+ if err != nil {
+ return total - len(p), err
+ }
+ if *bytesInBlock == blockSize {
+ err = onBlockWritten(len(p))
+ if err != nil {
+ return total - len(p), err
+ }
+ *bytesInBlock = 0
+ *onlyNullBytesInBlock = true
+ }
+ }
+ return total, nil
+}
+
+// level is a hash.Hash that is used to aggregate the checksums produced by the level hierarchically beneath it.
+// It is used to represent any level-n hash, except for level-0.
+type level struct {
+ checksum [Size]byte // aggregated checksum of this level
+ sumCount uint32 // number of sums contained in this level so far
+ bytesInHasher uint32 // number of bytes written into hasher so far
+ onlyNullBytesInHasher bool // whether the hasher only contains null-bytes so far
+ hasher hash.Hash
+}
+
+// NewLevel returns a new hash.Hash computing any level-n hash, except level-0.
+func NewLevel() hash.Hash {
+ l := &level{}
+ l.Reset()
+ return l
+}
+
+// Add takes a position-embedded SHA-1 checksum and adds it to the level.
+func (l *level) Add(sha1sum []byte) {
+ var tmp uint
+ var carry bool
+ for i := Size - 1; i >= 0; i-- {
+ tmp = uint(sha1sum[i]) + uint(l.checksum[i])
+ if carry {
+ tmp++
+ }
+ carry = tmp > 255
+ l.checksum[i] = byte(tmp)
+ }
+}
+
+// IsFull returns whether the number of checksums added to this level reached its capacity.
+func (l *level) IsFull() bool {
+ return l.sumCount >= sumsPerLevel
+}
+
+// Write (via the embedded io.Writer interface) adds more data to the running hash.
+// Contrary to the specification from hash.Hash, this DOES return an error,
+// specifically ErrorHashFull if and only if IsFull() returns true.
+func (l *level) Write(p []byte) (n int, err error) {
+ if l.IsFull() {
+ return 0, ErrorHashFull
+ }
+ onBlockWritten := func(remaining int) error {
+ if !l.onlyNullBytesInHasher {
+ c, err := l.hasher.Write([]byte{byte(l.sumCount)})
+ l.bytesInHasher += uint32(c)
+ if err != nil {
+ return err
+ }
+ l.Add(l.hasher.Sum(nil))
+ }
+ l.sumCount++
+ l.hasher.Reset()
+ if remaining > 0 && l.IsFull() {
+ return ErrorHashFull
+ }
+ return nil
+ }
+ return writeByBlock(p, l.hasher, uint32(l.BlockSize()), &l.bytesInHasher, &l.onlyNullBytesInHasher, onBlockWritten)
+}
+
+// Sum appends the current hash to b and returns the resulting slice.
+// It does not change the underlying hash state.
+func (l *level) Sum(b []byte) []byte {
+ return append(b, l.checksum[:]...)
+}
+
+// Reset resets the Hash to its initial state.
+func (l *level) Reset() {
+ l.checksum = zeroSum // clear the current checksum
+ l.sumCount = 0
+ l.bytesInHasher = 0
+ l.onlyNullBytesInHasher = true
+ l.hasher = sha1.New()
+}
+
+// Size returns the number of bytes Sum will return.
+func (l *level) Size() int {
+ return Size
+}
+
+// BlockSize returns the hash's underlying block size.
+// The Write method must be able to accept any amount
+// of data, but it may operate more efficiently if all writes
+// are a multiple of the block size.
+func (l *level) BlockSize() int {
+ return Size
+}
+
+// MarshalBinary encodes the hash into a binary form and returns the result.
+func (l *level) MarshalBinary() ([]byte, error) {
+ b := make([]byte, Size+4+4+1)
+ copy(b, l.checksum[:])
+ binary.BigEndian.PutUint32(b[Size:], l.sumCount)
+ binary.BigEndian.PutUint32(b[Size+4:], l.bytesInHasher)
+ if l.onlyNullBytesInHasher {
+ b[Size+4+4] = 1
+ }
+ encodedHasher, err := l.hasher.(encoding.BinaryMarshaler).MarshalBinary()
+ if err != nil {
+ return nil, err
+ }
+ b = append(b, encodedHasher...)
+ return b, nil
+}
+
+// UnmarshalBinary decodes the binary form generated by MarshalBinary.
+// The hash will replace its internal state accordingly.
+func (l *level) UnmarshalBinary(b []byte) error {
+ if len(b) < Size+4+4+1 {
+ return ErrorInvalidEncoding
+ }
+ copy(l.checksum[:], b)
+ l.sumCount = binary.BigEndian.Uint32(b[Size:])
+ l.bytesInHasher = binary.BigEndian.Uint32(b[Size+4:])
+ switch b[Size+4+4] {
+ case 0:
+ l.onlyNullBytesInHasher = false
+ case 1:
+ l.onlyNullBytesInHasher = true
+ default:
+ return ErrorInvalidEncoding
+ }
+ err := l.hasher.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[Size+4+4+1:])
+ return err
+}
+
+// hidriveHash is the hash computing the actual checksum used by HiDrive by combining multiple level-hashes.
+type hidriveHash struct {
+ levels []*level // collection of level-hashes, one for each level starting at level-1
+ lastSumWritten [Size]byte // the last checksum written to any of the levels
+ bytesInBlock uint32 // bytes written into blockHash so far
+ onlyNullBytesInBlock bool // whether the hasher only contains null-bytes so far
+ blockHash hash.Hash
+}
+
+// New returns a new hash.Hash computing the HiDrive checksum.
+func New() hash.Hash {
+ h := &hidriveHash{}
+ h.Reset()
+ return h
+}
+
+// aggregateToLevel writes the checksum to the level at the given index
+// and if necessary propagates any changes to levels above.
+func (h *hidriveHash) aggregateToLevel(index int, sum []byte) {
+ for i := index; ; i++ {
+ if i >= len(h.levels) {
+ h.levels = append(h.levels, NewLevel().(*level))
+ }
+ _, err := h.levels[i].Write(sum)
+ copy(h.lastSumWritten[:], sum)
+ if err != nil {
+ panic(fmt.Errorf("level-hash should not have produced an error: %w", err))
+ }
+ if !h.levels[i].IsFull() {
+ break
+ }
+ sum = h.levels[i].Sum(nil)
+ h.levels[i].Reset()
+ }
+}
+
+// Write (via the embedded io.Writer interface) adds more data to the running hash.
+// It never returns an error.
+func (h *hidriveHash) Write(p []byte) (n int, err error) {
+ onBlockWritten := func(remaining int) error {
+ var sum []byte
+ if h.onlyNullBytesInBlock {
+ sum = zeroSum[:]
+ } else {
+ sum = h.blockHash.Sum(nil)
+ }
+ h.blockHash.Reset()
+ h.aggregateToLevel(0, sum)
+ return nil
+ }
+ return writeByBlock(p, h.blockHash, uint32(BlockSize), &h.bytesInBlock, &h.onlyNullBytesInBlock, onBlockWritten)
+}
+
+// Sum appends the current hash to b and returns the resulting slice.
+// It does not change the underlying hash state.
+func (h *hidriveHash) Sum(b []byte) []byte {
+ // Save internal state.
+ state, err := h.MarshalBinary()
+ if err != nil {
+ panic(fmt.Errorf("saving the internal state should not have produced an error: %w", err))
+ }
+
+ if h.bytesInBlock > 0 {
+ // Fill remainder of block with null-bytes.
+ filler := make([]byte, h.BlockSize()-int(h.bytesInBlock))
+ _, err = h.Write(filler)
+ if err != nil {
+ panic(fmt.Errorf("filling with null-bytes should not have an error: %w", err))
+ }
+ }
+
+ checksum := zeroSum
+ for i := 0; i < len(h.levels); i++ {
+ level := h.levels[i]
+ if i < len(h.levels)-1 {
+ // Aggregate non-empty non-final levels.
+ if level.sumCount >= 1 {
+ h.aggregateToLevel(i+1, level.Sum(nil))
+ level.Reset()
+ }
+ } else {
+ // Determine sum of final level.
+ if level.sumCount > 1 {
+ copy(checksum[:], level.Sum(nil))
+ } else {
+ // This is needed, otherwise there is no way to return
+ // the non-position-embedded checksum.
+ checksum = h.lastSumWritten
+ }
+ }
+ }
+
+ // Restore internal state.
+ err = h.UnmarshalBinary(state)
+ if err != nil {
+ panic(fmt.Errorf("restoring the internal state should not have produced an error: %w", err))
+ }
+
+ return append(b, checksum[:]...)
+}
+
+// Reset resets the Hash to its initial state.
+func (h *hidriveHash) Reset() {
+ h.levels = nil
+ h.lastSumWritten = zeroSum // clear the last written checksum
+ h.bytesInBlock = 0
+ h.onlyNullBytesInBlock = true
+ h.blockHash = sha1.New()
+}
+
+// Size returns the number of bytes Sum will return.
+func (h *hidriveHash) Size() int {
+ return Size
+}
+
+// BlockSize returns the hash's underlying block size.
+// The Write method must be able to accept any amount
+// of data, but it may operate more efficiently if all writes
+// are a multiple of the block size.
+func (h *hidriveHash) BlockSize() int {
+ return BlockSize
+}
+
+// MarshalBinary encodes the hash into a binary form and returns the result.
+func (h *hidriveHash) MarshalBinary() ([]byte, error) {
+ b := make([]byte, Size+4+1+8)
+ copy(b, h.lastSumWritten[:])
+ binary.BigEndian.PutUint32(b[Size:], h.bytesInBlock)
+ if h.onlyNullBytesInBlock {
+ b[Size+4] = 1
+ }
+
+ binary.BigEndian.PutUint64(b[Size+4+1:], uint64(len(h.levels)))
+ for _, level := range h.levels {
+ encodedLevel, err := level.MarshalBinary()
+ if err != nil {
+ return nil, err
+ }
+ encodedLength := make([]byte, 8)
+ binary.BigEndian.PutUint64(encodedLength, uint64(len(encodedLevel)))
+ b = append(b, encodedLength...)
+ b = append(b, encodedLevel...)
+ }
+ encodedBlockHash, err := h.blockHash.(encoding.BinaryMarshaler).MarshalBinary()
+ if err != nil {
+ return nil, err
+ }
+ b = append(b, encodedBlockHash...)
+ return b, nil
+}
+
+// UnmarshalBinary decodes the binary form generated by MarshalBinary.
+// The hash will replace its internal state accordingly.
+func (h *hidriveHash) UnmarshalBinary(b []byte) error {
+ if len(b) < Size+4+1+8 {
+ return ErrorInvalidEncoding
+ }
+ copy(h.lastSumWritten[:], b)
+ h.bytesInBlock = binary.BigEndian.Uint32(b[Size:])
+ switch b[Size+4] {
+ case 0:
+ h.onlyNullBytesInBlock = false
+ case 1:
+ h.onlyNullBytesInBlock = true
+ default:
+ return ErrorInvalidEncoding
+ }
+
+ amount := binary.BigEndian.Uint64(b[Size+4+1:])
+ h.levels = make([]*level, int(amount))
+ offset := Size + 4 + 1 + 8
+ for i := range h.levels {
+ length := int(binary.BigEndian.Uint64(b[offset:]))
+ offset += 8
+ h.levels[i] = NewLevel().(*level)
+ err := h.levels[i].UnmarshalBinary(b[offset : offset+length])
+ if err != nil {
+ return err
+ }
+ offset += length
+ }
+ err := h.blockHash.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[offset:])
+ return err
+}
+
+// Sum returns the HiDrive checksum of the data.
+func Sum(data []byte) [Size]byte {
+ h := New().(*hidriveHash)
+ _, _ = h.Write(data)
+ var result [Size]byte
+ copy(result[:], h.Sum(nil))
+ return result
+}
+
+// Check the interfaces are satisfied.
+var (
+ _ hash.Hash = (*level)(nil)
+ _ encoding.BinaryMarshaler = (*level)(nil)
+ _ encoding.BinaryUnmarshaler = (*level)(nil)
+ _ internal.LevelHash = (*level)(nil)
+ _ hash.Hash = (*hidriveHash)(nil)
+ _ encoding.BinaryMarshaler = (*hidriveHash)(nil)
+ _ encoding.BinaryUnmarshaler = (*hidriveHash)(nil)
+)
diff --git a/backend/hidrive/hidrivehash/hidrivehash_test.go b/backend/hidrive/hidrivehash/hidrivehash_test.go
new file mode 100644
index 0000000000000..d27970c3462e1
--- /dev/null
+++ b/backend/hidrive/hidrivehash/hidrivehash_test.go
@@ -0,0 +1,395 @@
+package hidrivehash_test
+
+import (
+ "crypto/sha1"
+ "encoding"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "testing"
+
+ "github.com/rclone/rclone/backend/hidrive/hidrivehash"
+ "github.com/rclone/rclone/backend/hidrive/hidrivehash/internal"
+ "github.com/stretchr/testify/assert"
+)
+
+// helper functions to set up test-tables
+
+func sha1ArrayAsSlice(sum [sha1.Size]byte) []byte {
+ return sum[:]
+}
+
+func mustDecode(hexstring string) []byte {
+ result, err := hex.DecodeString(hexstring)
+ if err != nil {
+ panic(err)
+ }
+ return result
+}
+
+// ------------------------------------------------------------
+
+var testTableLevelPositionEmbedded = []struct {
+ ins [][]byte
+ outs [][]byte
+ name string
+}{
+ {
+ [][]byte{
+ sha1ArrayAsSlice([20]byte{245, 202, 195, 223, 121, 198, 189, 112, 138, 202, 222, 2, 146, 156, 127, 16, 208, 233, 98, 88}),
+ sha1ArrayAsSlice([20]byte{78, 188, 156, 219, 173, 54, 81, 55, 47, 220, 222, 207, 201, 21, 57, 252, 255, 239, 251, 186}),
+ },
+ [][]byte{
+ sha1ArrayAsSlice([20]byte{245, 202, 195, 223, 121, 198, 189, 112, 138, 202, 222, 2, 146, 156, 127, 16, 208, 233, 98, 88}),
+ sha1ArrayAsSlice([20]byte{68, 135, 96, 187, 38, 253, 14, 167, 186, 167, 188, 210, 91, 177, 185, 13, 208, 217, 94, 18}),
+ },
+ "documentation-v3.2rev27-example L0 (position-embedded)",
+ },
+ {
+ [][]byte{
+ sha1ArrayAsSlice([20]byte{68, 254, 92, 166, 52, 37, 104, 180, 22, 123, 249, 144, 182, 78, 64, 74, 57, 117, 225, 195}),
+ sha1ArrayAsSlice([20]byte{75, 211, 153, 190, 125, 179, 67, 49, 60, 149, 98, 246, 142, 20, 11, 254, 159, 162, 129, 237}),
+ sha1ArrayAsSlice([20]byte{150, 2, 9, 153, 97, 153, 189, 104, 147, 14, 77, 203, 244, 243, 25, 212, 67, 48, 111, 107}),
+ },
+ [][]byte{
+ sha1ArrayAsSlice([20]byte{68, 254, 92, 166, 52, 37, 104, 180, 22, 123, 249, 144, 182, 78, 64, 74, 57, 117, 225, 195}),
+ sha1ArrayAsSlice([20]byte{144, 209, 246, 100, 177, 216, 171, 229, 83, 17, 92, 135, 68, 98, 76, 72, 217, 24, 99, 176}),
+ sha1ArrayAsSlice([20]byte{38, 211, 255, 254, 19, 114, 105, 77, 230, 31, 170, 83, 57, 85, 102, 29, 28, 72, 211, 27}),
+ },
+ "documentation-example L0 (position-embedded)",
+ },
+ {
+ [][]byte{
+ sha1ArrayAsSlice([20]byte{173, 123, 132, 245, 176, 172, 43, 183, 121, 40, 66, 252, 101, 249, 188, 193, 160, 189, 2, 116}),
+ sha1ArrayAsSlice([20]byte{40, 34, 8, 238, 37, 5, 237, 184, 79, 105, 10, 167, 171, 254, 13, 229, 132, 112, 254, 8}),
+ sha1ArrayAsSlice([20]byte{39, 112, 26, 86, 190, 35, 100, 101, 28, 131, 122, 191, 254, 144, 239, 107, 253, 124, 104, 203}),
+ },
+ [][]byte{
+ sha1ArrayAsSlice([20]byte{173, 123, 132, 245, 176, 172, 43, 183, 121, 40, 66, 252, 101, 249, 188, 193, 160, 189, 2, 116}),
+ sha1ArrayAsSlice([20]byte{213, 157, 141, 227, 213, 178, 25, 111, 200, 145, 77, 164, 17, 247, 202, 167, 37, 46, 0, 124}),
+ sha1ArrayAsSlice([20]byte{253, 13, 168, 58, 147, 213, 125, 212, 229, 20, 200, 100, 16, 136, 186, 19, 34, 170, 105, 71}),
+ },
+ "documentation-example L1 (position-embedded)",
+ },
+}
+
+var testTableLevel = []struct {
+ ins [][]byte
+ outs [][]byte
+ name string
+}{
+ {
+ [][]byte{
+ mustDecode("09f077820a8a41f34a639f2172f1133b1eafe4e6"),
+ mustDecode("09f077820a8a41f34a639f2172f1133b1eafe4e6"),
+ mustDecode("09f077820a8a41f34a639f2172f1133b1eafe4e6"),
+ },
+ [][]byte{
+ mustDecode("44fe5ca6342568b4167bf990b64e404a3975e1c3"),
+ mustDecode("90d1f664b1d8abe553115c8744624c48d91863b0"),
+ mustDecode("26d3fffe1372694de61faa533955661d1c48d31b"),
+ },
+ "documentation-example L0",
+ },
+ {
+ [][]byte{
+ mustDecode("75a9f88fb219ef1dd31adf41c93e2efaac8d0245"),
+ mustDecode("daedc425199501b1e86b5eaba5649cbde205e6ae"),
+ mustDecode("286ac5283f99c4e0f11683900a3e39661c375dd6"),
+ },
+ [][]byte{
+ mustDecode("ad7b84f5b0ac2bb7792842fc65f9bcc1a0bd0274"),
+ mustDecode("d59d8de3d5b2196fc8914da411f7caa7252e007c"),
+ mustDecode("fd0da83a93d57dd4e514c8641088ba1322aa6947"),
+ },
+ "documentation-example L1",
+ },
+ {
+ [][]byte{
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("75a9f88fb219ef1dd31adf41c93e2efaac8d0245"),
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("daedc425199501b1e86b5eaba5649cbde205e6ae"),
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("286ac5283f99c4e0f11683900a3e39661c375dd6"),
+ mustDecode("0000000000000000000000000000000000000000"),
+ },
+ [][]byte{
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("0000000000000000000000000000000000000000"),
+ mustDecode("a197464ec19f2b2b2bc6b21f6c939c7e57772843"),
+ mustDecode("a197464ec19f2b2b2bc6b21f6c939c7e57772843"),
+ mustDecode("b04769357aa4eb4b52cd5bec6935bc8f977fa3a1"),
+ mustDecode("b04769357aa4eb4b52cd5bec6935bc8f977fa3a1"),
+ mustDecode("b04769357aa4eb4b52cd5bec6935bc8f977fa3a1"),
+ mustDecode("b04769357aa4eb4b52cd5bec6935bc8f977fa3a1"),
+ mustDecode("8f56351897b4e1d100646fa122c924347721b2f5"),
+ mustDecode("8f56351897b4e1d100646fa122c924347721b2f5"),
+ },
+ "mixed-with-empties",
+ },
+}
+
+var testTable = []struct {
+ data []byte
+ // pattern describes how to use data to construct the hash-input.
+ // For every entry n at even indices this repeats the data n times.
+ // For every entry m at odd indices this repeats a null-byte m times.
+ // The input-data is constructed by concatinating the results in order.
+ pattern []int64
+ out []byte
+ name string
+}{
+ {
+ []byte("#ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz\n"),
+ []int64{64},
+ mustDecode("09f077820a8a41f34a639f2172f1133b1eafe4e6"),
+ "documentation-example L0",
+ },
+ {
+ []byte("#ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz\n"),
+ []int64{64 * 256},
+ mustDecode("75a9f88fb219ef1dd31adf41c93e2efaac8d0245"),
+ "documentation-example L1",
+ },
+ {
+ []byte("#ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz\n"),
+ []int64{64 * 256, 0, 64 * 128, 4096 * 128, 64*2 + 32},
+ mustDecode("fd0da83a93d57dd4e514c8641088ba1322aa6947"),
+ "documentation-example L2",
+ },
+ {
+ []byte("hello rclone\n"),
+ []int64{316},
+ mustDecode("72370f9c18a2c20b31d71f3f4cee7a3cd2703737"),
+ "not-block-aligned",
+ },
+ {
+ []byte("hello rclone\n"),
+ []int64{13, 4096 * 3, 4},
+ mustDecode("a6990b81791f0d2db750b38f046df321c975aa60"),
+ "not-block-aligned-with-null-bytes",
+ },
+ {
+ []byte{},
+ []int64{},
+ mustDecode("0000000000000000000000000000000000000000"),
+ "empty",
+ },
+ {
+ []byte{},
+ []int64{0, 4096 * 256 * 256},
+ mustDecode("0000000000000000000000000000000000000000"),
+ "null-bytes",
+ },
+}
+
+// ------------------------------------------------------------
+
+func TestLevelAdd(t *testing.T) {
+ for _, test := range testTableLevelPositionEmbedded {
+ l := hidrivehash.NewLevel().(internal.LevelHash)
+ t.Run(test.name, func(t *testing.T) {
+ for i := range test.ins {
+ l.Add(test.ins[i])
+ assert.Equal(t, test.outs[i], l.Sum(nil))
+ }
+ })
+ }
+}
+
+func TestLevelWrite(t *testing.T) {
+ for _, test := range testTableLevel {
+ l := hidrivehash.NewLevel()
+ t.Run(test.name, func(t *testing.T) {
+ for i := range test.ins {
+ l.Write(test.ins[i])
+ assert.Equal(t, test.outs[i], l.Sum(nil))
+ }
+ })
+ }
+}
+
+func TestLevelIsFull(t *testing.T) {
+ content := [hidrivehash.Size]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}
+ l := hidrivehash.NewLevel()
+ for i := 0; i < 256; i++ {
+ assert.False(t, l.(internal.LevelHash).IsFull())
+ written, err := l.Write(content[:])
+ assert.Equal(t, len(content), written)
+ if !assert.NoError(t, err) {
+ t.FailNow()
+ }
+ }
+ assert.True(t, l.(internal.LevelHash).IsFull())
+ written, err := l.Write(content[:])
+ assert.True(t, l.(internal.LevelHash).IsFull())
+ assert.Equal(t, 0, written)
+ assert.ErrorIs(t, err, hidrivehash.ErrorHashFull)
+}
+
+func TestLevelReset(t *testing.T) {
+ l := hidrivehash.NewLevel()
+ zeroHash := l.Sum(nil)
+ _, err := l.Write([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19})
+ if assert.NoError(t, err) {
+ assert.NotEqual(t, zeroHash, l.Sum(nil))
+ l.Reset()
+ assert.Equal(t, zeroHash, l.Sum(nil))
+ }
+}
+
+func TestLevelSize(t *testing.T) {
+ l := hidrivehash.NewLevel()
+ assert.Equal(t, 20, l.Size())
+}
+
+func TestLevelBlockSize(t *testing.T) {
+ l := hidrivehash.NewLevel()
+ assert.Equal(t, 20, l.BlockSize())
+}
+
+func TestLevelBinaryMarshaler(t *testing.T) {
+ content := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}
+ l := hidrivehash.NewLevel().(internal.LevelHash)
+ l.Write(content[:10])
+ encoded, err := l.MarshalBinary()
+ if assert.NoError(t, err) {
+ d := hidrivehash.NewLevel().(internal.LevelHash)
+ err = d.UnmarshalBinary(encoded)
+ if assert.NoError(t, err) {
+ assert.Equal(t, l.Sum(nil), d.Sum(nil))
+ l.Write(content[10:])
+ d.Write(content[10:])
+ assert.Equal(t, l.Sum(nil), d.Sum(nil))
+ }
+ }
+}
+
+func TestLevelInvalidEncoding(t *testing.T) {
+ l := hidrivehash.NewLevel().(internal.LevelHash)
+ err := l.UnmarshalBinary([]byte{})
+ assert.ErrorIs(t, err, hidrivehash.ErrorInvalidEncoding)
+}
+
+// ------------------------------------------------------------
+
+type infiniteReader struct {
+ source []byte
+ offset int
+}
+
+func (m *infiniteReader) Read(b []byte) (int, error) {
+ count := copy(b, m.source[m.offset:])
+ m.offset += count
+ m.offset %= len(m.source)
+ return count, nil
+}
+
+func writeInChunks(writer io.Writer, chunkSize int64, data []byte, pattern []int64) error {
+ readers := make([]io.Reader, len(pattern))
+ nullBytes := [4096]byte{}
+ for i, n := range pattern {
+ if i%2 == 0 {
+ readers[i] = io.LimitReader(&infiniteReader{data, 0}, n*int64(len(data)))
+ } else {
+ readers[i] = io.LimitReader(&infiniteReader{nullBytes[:], 0}, n)
+ }
+ }
+ reader := io.MultiReader(readers...)
+ for {
+ _, err := io.CopyN(writer, reader, chunkSize)
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ return err
+ }
+ }
+}
+
+func TestWrite(t *testing.T) {
+ for _, test := range testTable {
+ t.Run(test.name, func(t *testing.T) {
+ h := hidrivehash.New()
+ err := writeInChunks(h, int64(h.BlockSize()), test.data, test.pattern)
+ if assert.NoError(t, err) {
+ normalSum := h.Sum(nil)
+ assert.Equal(t, test.out, normalSum)
+ // Test if different block-sizes produce differing results.
+ for _, blockSize := range []int64{397, 512, 4091, 8192, 10000} {
+ t.Run(fmt.Sprintf("block-size %v", blockSize), func(t *testing.T) {
+ h := hidrivehash.New()
+ err := writeInChunks(h, blockSize, test.data, test.pattern)
+ if assert.NoError(t, err) {
+ assert.Equal(t, normalSum, h.Sum(nil))
+ }
+ })
+ }
+ }
+ })
+ }
+}
+
+func TestReset(t *testing.T) {
+ h := hidrivehash.New()
+ zeroHash := h.Sum(nil)
+ _, err := h.Write([]byte{1})
+ if assert.NoError(t, err) {
+ assert.NotEqual(t, zeroHash, h.Sum(nil))
+ h.Reset()
+ assert.Equal(t, zeroHash, h.Sum(nil))
+ }
+}
+
+func TestSize(t *testing.T) {
+ h := hidrivehash.New()
+ assert.Equal(t, 20, h.Size())
+}
+
+func TestBlockSize(t *testing.T) {
+ h := hidrivehash.New()
+ assert.Equal(t, 4096, h.BlockSize())
+}
+
+func TestBinaryMarshaler(t *testing.T) {
+ for _, test := range testTable {
+ h := hidrivehash.New()
+ d := hidrivehash.New()
+ half := len(test.pattern) / 2
+ t.Run(test.name, func(t *testing.T) {
+ err := writeInChunks(h, int64(h.BlockSize()), test.data, test.pattern[:half])
+ assert.NoError(t, err)
+ encoded, err := h.(encoding.BinaryMarshaler).MarshalBinary()
+ if assert.NoError(t, err) {
+ err = d.(encoding.BinaryUnmarshaler).UnmarshalBinary(encoded)
+ if assert.NoError(t, err) {
+ assert.Equal(t, h.Sum(nil), d.Sum(nil))
+ err = writeInChunks(h, int64(h.BlockSize()), test.data, test.pattern[half:])
+ assert.NoError(t, err)
+ err = writeInChunks(d, int64(d.BlockSize()), test.data, test.pattern[half:])
+ assert.NoError(t, err)
+ assert.Equal(t, h.Sum(nil), d.Sum(nil))
+ }
+ }
+ })
+ }
+}
+
+func TestInvalidEncoding(t *testing.T) {
+ h := hidrivehash.New()
+ err := h.(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte{})
+ assert.ErrorIs(t, err, hidrivehash.ErrorInvalidEncoding)
+}
+
+func TestSum(t *testing.T) {
+ assert.Equal(t, [hidrivehash.Size]byte{}, hidrivehash.Sum([]byte{}))
+ content := []byte{1}
+ h := hidrivehash.New()
+ h.Write(content)
+ sum := hidrivehash.Sum(content)
+ assert.Equal(t, h.Sum(nil), sum[:])
+}
diff --git a/backend/hidrive/hidrivehash/internal/internal.go b/backend/hidrive/hidrivehash/internal/internal.go
new file mode 100644
index 0000000000000..f1596a9e68d42
--- /dev/null
+++ b/backend/hidrive/hidrivehash/internal/internal.go
@@ -0,0 +1,17 @@
+package internal
+
+import (
+ "encoding"
+ "hash"
+)
+
+// LevelHash is an internal interface for level-hashes.
+type LevelHash interface {
+ encoding.BinaryMarshaler
+ encoding.BinaryUnmarshaler
+ hash.Hash
+ // Add takes a position-embedded checksum and adds it to the level.
+ Add(sum []byte)
+ // IsFull returns whether the number of checksums added to this level reached its capacity.
+ IsFull() bool
+}
diff --git a/bin/make_manual.py b/bin/make_manual.py
index 335f4c8148111..fa14b36a3748c 100755
--- a/bin/make_manual.py
+++ b/bin/make_manual.py
@@ -47,6 +47,7 @@
"googlephotos.md",
"hasher.md",
"hdfs.md",
+ "hidrive.md",
"http.md",
"hubic.md",
"internetarchive.md",
diff --git a/docs/content/_index.md b/docs/content/_index.md
index b6e2ac4d1c0ac..9d0f86bf439d9 100644
--- a/docs/content/_index.md
+++ b/docs/content/_index.md
@@ -128,6 +128,7 @@ WebDAV or S3, that work out of the box.)
{{< provider name="Google Photos" home="https://www.google.com/photos/about/" config="/googlephotos/" >}}
{{< provider name="HDFS" home="https://hadoop.apache.org/" config="/hdfs/" >}}
{{< provider name="Hetzner Storage Box" home="https://www.hetzner.com/storage/storage-box" config="/sftp/#hetzner-storage-box" >}}
+{{< provider name="HiDrive" home="https://www.strato.de/cloud-speicher/" config="/hidrive/" >}}
{{< provider name="HTTP" home="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" config="/http/" >}}
{{< provider name="Hubic" home="https://hubic.com/" config="/hubic/" >}}
{{< provider name="Internet Archive" home="https://archive.org/" config="/internetarchive/" >}}
diff --git a/docs/content/docs.md b/docs/content/docs.md
index a2bc38b078249..a71728cc7e98a 100644
--- a/docs/content/docs.md
+++ b/docs/content/docs.md
@@ -49,6 +49,7 @@ See the following for detailed instructions for
* [Google Photos](/googlephotos/)
* [Hasher](/hasher/) - to handle checksums for other remotes
* [HDFS](/hdfs/)
+ * [HiDrive](/hidrive/)
* [HTTP](/http/)
* [Hubic](/hubic/)
* [Internet Archive](/internetarchive/)
diff --git a/docs/content/hidrive.md b/docs/content/hidrive.md
new file mode 100644
index 0000000000000..2d667a9e6d255
--- /dev/null
+++ b/docs/content/hidrive.md
@@ -0,0 +1,461 @@
+---
+title: "HiDrive"
+description: "Rclone docs for HiDrive"
+---
+
+# {{< icon "fa fa-cloud" >}} HiDrive
+
+Paths are specified as `remote:path`
+
+Paths may be as deep as required, e.g. `remote:directory/subdirectory`.
+
+The initial setup for hidrive involves getting a token from HiDrive
+which you need to do in your browser.
+`rclone config` walks you through it.
+
+## Configuration
+
+Here is an example of how to make a remote called `remote`. First run:
+
+ rclone config
+
+This will guide you through an interactive setup process:
+
+```
+No remotes found - make a new one
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+name> remote
+Type of storage to configure.
+Choose a number from below, or type in your own value
+[snip]
+XX / HiDrive
+ \ "hidrive"
+[snip]
+Storage> hidrive
+OAuth Client Id - Leave blank normally.
+client_id>
+OAuth Client Secret - Leave blank normally.
+client_secret>
+Access permissions that rclone should use when requesting access from HiDrive.
+Leave blank normally.
+scope_access>
+Edit advanced config?
+y/n> n
+Use auto config?
+y/n> y
+If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth?state=xxxxxxxxxxxxxxxxxxxxxx
+Log in and authorize rclone for access
+Waiting for code...
+Got code
+--------------------
+[remote]
+type = hidrive
+token = {"access_token":"xxxxxxxxxxxxxxxxxxxx","token_type":"Bearer","refresh_token":"xxxxxxxxxxxxxxxxxxxxxxx","expiry":"xxxxxxxxxxxxxxxxxxxxxxx"}
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+```
+
+**You should be aware that OAuth-tokens can be used to access your account
+and hence should not be shared with other persons.**
+See the [below section](#keeping-your-tokens-safe) for more information.
+
+See the [remote setup docs](/remote_setup/) for how to set it up on a
+machine with no Internet browser available.
+
+Note that rclone runs a webserver on your local machine to collect the
+token as returned from HiDrive. This only runs from the moment it opens
+your browser to the moment you get back the verification code.
+The webserver runs on `http://127.0.0.1:53682/`.
+If local port `53682` is protected by a firewall you may need to temporarily
+unblock the firewall to complete authorization.
+
+Once configured you can then use `rclone` like this,
+
+List directories in top level of your HiDrive root folder
+
+ rclone lsd remote:
+
+List all the files in your HiDrive filesystem
+
+ rclone ls remote:
+
+To copy a local directory to a HiDrive directory called backup
+
+ rclone copy /home/source remote:backup
+
+### Keeping your tokens safe
+
+Any OAuth-tokens will be stored by rclone in the remote's configuration file as unencrypted text.
+Anyone can use a valid refresh-token to access your HiDrive filesystem without knowing your password.
+Therefore you should make sure no one else can access your configuration.
+
+It is possible to encrypt rclone's configuration file.
+You can find information on securing your configuration file by viewing the [configuration encryption docs](/docs/#configuration-encryption).
+
+### Invalid refresh token
+
+As can be verified [here](https://developer.hidrive.com/basics-flows/),
+each `refresh_token` (for Native Applications) is valid for 60 days.
+If used to access HiDrivei, its validity will be automatically extended.
+
+This means that if you
+
+ * Don't use the HiDrive remote for 60 days
+
+then rclone will return an error which includes a text
+that implies the refresh token is *invalid* or *expired*.
+
+To fix this you will need to authorize rclone to access your HiDrive account again.
+
+Using
+
+ rclone config reconnect remote:
+
+the process is very similar to the process of initial setup exemplified before.
+
+### Modified time and hashes
+
+HiDrive allows modification times to be set on objects accurate to 1 second.
+
+HiDrive supports [its own hash type](https://static.hidrive.com/dev/0001)
+which is used to verify the integrety of file contents after successful transfers.
+
+### Restricted filename characters
+
+HiDrive cannot store files or folders that include
+`/` (0x2F) or null-bytes (0x00) in their name.
+Any other characters can be used in the names of files or folders.
+Additionally, files or folders cannot be named either of the following: `.` or `..`
+
+Therefore rclone will automatically replace these characters,
+if files or folders are stored or accessed with such names.
+
+You can read about how this filename encoding works in general
+[here](overview/#restricted-filenames).
+
+Keep in mind that HiDrive only supports file or folder names
+with a length of 255 characters or less.
+
+### Transfers
+
+HiDrive limits file sizes per single request to a maximum of 2 GiB.
+To allow storage of larger files and allow for better upload performance,
+the hidrive backend will use a chunked transfer for files larger than 96 MiB.
+Rclone will upload multiple parts/chunks of the file at the same time.
+Chunks in the process of being uploaded are buffered in memory,
+so you may want to restrict this behaviour on systems with limited resources.
+
+You can customize this behaviour using the following options:
+
+* `chunk_size`: size of file parts
+* `upload_cutoff`: files larger or equal to this in size will use a chunked transfer
+* `upload_concurrency`: number of file-parts to upload at the same time
+
+See the below section about configuration options for more details.
+
+### Root folder
+
+You can set the root folder for rclone.
+This is the directory that rclone considers to be the root of your HiDrive.
+
+Usually, you will leave this blank, and rclone will use the root of the account.
+
+However, you can set this to restrict rclone to a specific folder hierarchy.
+
+This works by prepending the contents of the `root_prefix` option
+to any paths accessed by rclone.
+For example, the following two ways to access the home directory are equivalent:
+
+ rclone lsd --hidrive-root-prefix="/users/test/" remote:path
+
+ rclone lsd remote:/users/test/path
+
+See the below section about configuration options for more details.
+
+### Directory member count
+
+By default, rclone will know the number of directory members contained in a directory.
+For example, `rclone lsd` uses this information.
+
+The acquisition of this information will result in additional time costs for HiDrive's API.
+When dealing with large directory structures, it may be desirable to circumvent this time cost,
+especially when this information is not explicitly needed.
+For this, the `disable_fetching_member_count` option can be used.
+
+See the below section about configuration options for more details.
+
+{{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/hidrive/hidrive.go then run make backenddocs" >}}
+### Standard options
+
+Here are the standard options specific to hidrive (HiDrive).
+
+#### --hidrive-client-id
+
+OAuth Client Id.
+
+Leave blank normally.
+
+Properties:
+
+- Config: client_id
+- Env Var: RCLONE_HIDRIVE_CLIENT_ID
+- Type: string
+- Required: false
+
+#### --hidrive-client-secret
+
+OAuth Client Secret.
+
+Leave blank normally.
+
+Properties:
+
+- Config: client_secret
+- Env Var: RCLONE_HIDRIVE_CLIENT_SECRET
+- Type: string
+- Required: false
+
+#### --hidrive-scope-access
+
+Access permissions that rclone should use when requesting access from HiDrive.
+
+Properties:
+
+- Config: scope_access
+- Env Var: RCLONE_HIDRIVE_SCOPE_ACCESS
+- Type: string
+- Default: "rw"
+- Examples:
+ - "rw"
+ - Read and write access to resources.
+ - "ro"
+ - Read-only access to resources.
+
+### Advanced options
+
+Here are the advanced options specific to hidrive (HiDrive).
+
+#### --hidrive-token
+
+OAuth Access Token as a JSON blob.
+
+Properties:
+
+- Config: token
+- Env Var: RCLONE_HIDRIVE_TOKEN
+- Type: string
+- Required: false
+
+#### --hidrive-auth-url
+
+Auth server URL.
+
+Leave blank to use the provider defaults.
+
+Properties:
+
+- Config: auth_url
+- Env Var: RCLONE_HIDRIVE_AUTH_URL
+- Type: string
+- Required: false
+
+#### --hidrive-token-url
+
+Token server url.
+
+Leave blank to use the provider defaults.
+
+Properties:
+
+- Config: token_url
+- Env Var: RCLONE_HIDRIVE_TOKEN_URL
+- Type: string
+- Required: false
+
+#### --hidrive-scope-role
+
+User-level that rclone should use when requesting access from HiDrive.
+
+Properties:
+
+- Config: scope_role
+- Env Var: RCLONE_HIDRIVE_SCOPE_ROLE
+- Type: string
+- Default: "user"
+- Examples:
+ - "user"
+ - User-level access to management permissions.
+ - This will be sufficient in most cases.
+ - "admin"
+ - Extensive access to management permissions.
+ - "owner"
+ - Full access to management permissions.
+
+#### --hidrive-root-prefix
+
+The root/parent folder for all paths.
+
+Fill in to use the specified folder as the parent for all paths given to the remote.
+This way rclone can use any folder as its starting point.
+
+Properties:
+
+- Config: root_prefix
+- Env Var: RCLONE_HIDRIVE_ROOT_PREFIX
+- Type: string
+- Default: "/"
+- Examples:
+ - "/"
+ - The topmost directory accessible by rclone.
+ - This will be equivalent with "root" if rclone uses a regular HiDrive user account.
+ - "root"
+ - The topmost directory of the HiDrive user account
+ - ""
+ - This specifies that there is no root-prefix for your paths.
+ - When using this you will always need to specify paths to this remote with a valid parent e.g. "remote:/path/to/dir" or "remote:root/path/to/dir".
+
+#### --hidrive-endpoint
+
+Endpoint for the service.
+
+This is the URL that API-calls will be made to.
+
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_HIDRIVE_ENDPOINT
+- Type: string
+- Default: "https://api.hidrive.strato.com/2.1"
+
+#### --hidrive-disable-fetching-member-count
+
+Do not fetch number of objects in directories unless it is absolutely necessary.
+
+Requests may be faster if the number of objects in subdirectories is not fetched.
+
+Properties:
+
+- Config: disable_fetching_member_count
+- Env Var: RCLONE_HIDRIVE_DISABLE_FETCHING_MEMBER_COUNT
+- Type: bool
+- Default: false
+
+#### --hidrive-disable-unicode-normalization
+
+Do not apply Unicode "Normalization Form C" to remote paths.
+
+In Unicode there are multiple valid representations for the same abstract character.
+They (should) result in the same visual appearance, but are represented by different byte-sequences.
+This is known as canonical equivalence.
+
+In HiDrive paths are always represented as byte-sequences.
+This means that two paths that are canonically equivalent (and therefore look the same) are treated as two distinct paths.
+As this behaviour may be undesired, by default rclone will apply unicode normalization to paths it will access.
+
+Properties:
+
+- Config: disable_unicode_normalization
+- Env Var: RCLONE_HIDRIVE_DISABLE_UNICODE_NORMALIZATION
+- Type: bool
+- Default: false
+
+#### --hidrive-chunk-size
+
+Chunksize for chunked uploads.
+
+Any files larger than the configured cutoff (or files of unknown size) will be uploaded in chunks of this size.
+
+The upper limit for this is 2147483647 bytes (about 2.000Gi).
+That is the maximum amount of bytes a single upload-operation will support.
+Setting this above the upper limit or to a negative value will cause uploads to fail.
+
+Setting this to larger values may increase the upload speed at the cost of using more memory.
+It can be set to smaller values smaller to save on memory.
+
+Properties:
+
+- Config: chunk_size
+- Env Var: RCLONE_HIDRIVE_CHUNK_SIZE
+- Type: SizeSuffix
+- Default: 48Mi
+
+#### --hidrive-upload-cutoff
+
+Cutoff/Threshold for chunked uploads.
+
+Any files larger than this will be uploaded in chunks of the configured chunksize.
+
+The upper limit for this is 2147483647 bytes (about 2.000Gi).
+That is the maximum amount of bytes a single upload-operation will support.
+Setting this above the upper limit will cause uploads to fail.
+
+Properties:
+
+- Config: upload_cutoff
+- Env Var: RCLONE_HIDRIVE_UPLOAD_CUTOFF
+- Type: SizeSuffix
+- Default: 96Mi
+
+#### --hidrive-upload-concurrency
+
+Concurrency for chunked uploads.
+
+This is the upper limit for how many transfers for the same file are running concurrently.
+Setting this above to a value smaller than 1 will cause uploads to deadlock.
+
+If you are uploading small numbers of large files over high-speed links
+and these uploads do not fully utilize your bandwidth, then increasing
+this may help to speed up the transfers.
+
+Properties:
+
+- Config: upload_concurrency
+- Env Var: RCLONE_HIDRIVE_UPLOAD_CONCURRENCY
+- Type: int
+- Default: 4
+
+#### --hidrive-encoding
+
+The encoding for the backend.
+
+See the [encoding section in the overview](/overview/#encoding) for more info.
+
+Properties:
+
+- Config: encoding
+- Env Var: RCLONE_HIDRIVE_ENCODING
+- Type: MultiEncoder
+- Default: Slash,Dot
+
+{{< rem autogenerated options stop >}}
+
+## Limitations
+
+### Symbolic links
+
+HiDrive is able to store symbolic links (*symlinks*) by design,
+for example, when unpacked from a zip archive.
+
+There exists no direct mechanism to manage native symlinks in remotes.
+As such this implementation has chosen to ignore any native symlinks present in the remote.
+rclone will not be able to access or show any symlinks stored in the hidrive-remote.
+This means symlinks cannot be individually removed, copied, or moved,
+except when removing, copying, or moving the parent folder.
+
+*This does not affect the `.rclonelink`-files
+that rclone uses to encode and store symbolic links.*
+
+### Sparse files
+
+It is possible to store sparse files in HiDrive.
+
+Note that copying a sparse file will expand the holes
+into null-byte (0x00) regions that will then consume disk space.
+Likewise, when downloading a sparse file,
+the resulting file will have null-byte regions in the place of file holes.
diff --git a/docs/content/overview.md b/docs/content/overview.md
index 74194f28f3f61..3f298c1ba34b6 100644
--- a/docs/content/overview.md
+++ b/docs/content/overview.md
@@ -30,6 +30,7 @@ Here is an overview of the major features of each cloud storage system.
| Google Drive | MD5 | R/W | No | Yes | R/W | - |
| Google Photos | - | - | No | Yes | R | - |
| HDFS | - | R/W | No | No | - | - |
+| HiDrive | HiDrive ¹² | R/W | No | No | - | - |
| HTTP | - | R | No | No | R | - |
| Hubic | MD5 | R/W | No | No | R/W | - |
| Internet Archive | MD5, SHA1, CRC32 | R/W ¹¹ | No | No | - | - |
@@ -93,6 +94,11 @@ for more details.
¹¹ Internet Archive requires option `wait_archive` to be set to a non-zero value
for full modtime support.
+¹² HiDrive supports [its own custom
+hash](https://static.hidrive.com/dev/0001).
+It combines SHA1 sums for each 4 KiB block hierarchically to a single
+top-level sum.
+
### Hash ###
The cloud storage system supports various hash types of the objects.
@@ -475,6 +481,7 @@ upon backend-specific capabilities.
| Google Drive | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Google Photos | No | No | No | No | No | No | No | No | No | No |
| HDFS | Yes | No | Yes | Yes | No | No | Yes | No | Yes | Yes |
+| HiDrive | Yes | Yes | Yes | Yes | No | No | Yes | No | No | Yes |
| HTTP | No | No | No | No | No | No | No | No | No | Yes |
| Hubic | Yes † | Yes | No | No | No | Yes | Yes | No | Yes | No |
| Internet Archive | No | Yes | No | No | Yes | Yes | No | Yes | Yes | No |
diff --git a/docs/layouts/chrome/navbar.html b/docs/layouts/chrome/navbar.html
index 32c05d3e0c346..f732756e32438 100644
--- a/docs/layouts/chrome/navbar.html
+++ b/docs/layouts/chrome/navbar.html
@@ -72,6 +72,7 @@
Google Photos
Hasher (better checksums for others)
HDFS (Hadoop Distributed Filesystem)
+ HiDrive
HTTP
Hubic
Internet Archive
diff --git a/fstest/test_all/config.yaml b/fstest/test_all/config.yaml
index 68f17c2ddd0a5..23cdee6db673c 100644
--- a/fstest/test_all/config.yaml
+++ b/fstest/test_all/config.yaml
@@ -133,6 +133,9 @@ backends:
remote: "TestGooglePhotos:"
tests:
- backend
+ - backend: "hidrive"
+ remote: "TestHiDrive:"
+ fastlist: false
- backend: "hubic"
remote: "TestHubic:"
fastlist: false
From 42dfadfa1b7c1be6f167b74539757dc9dc7ba62a Mon Sep 17 00:00:00 2001
From: "Lesmiscore (Naoya Ozaki)"
Date: Sat, 9 Jul 2022 07:47:50 +0900
Subject: [PATCH 168/560] internetarchive: add support for Metadata
---
backend/internetarchive/internetarchive.go | 196 +++++++++++++++++++--
docs/content/internetarchive.md | 27 +++
docs/content/overview.md | 2 +-
3 files changed, 208 insertions(+), 17 deletions(-)
diff --git a/backend/internetarchive/internetarchive.go b/backend/internetarchive/internetarchive.go
index ce021e35f54a6..9d4dd36dba708 100644
--- a/backend/internetarchive/internetarchive.go
+++ b/backend/internetarchive/internetarchive.go
@@ -38,6 +38,84 @@ func init() {
Name: "internetarchive",
Description: "Internet Archive",
NewFs: NewFs,
+
+ MetadataInfo: &fs.MetadataInfo{
+ System: map[string]fs.MetadataHelp{
+ "name": {
+ Help: "Full file path, without the bucket part",
+ Type: "filename",
+ Example: "backend/internetarchive/internetarchive.go",
+ },
+ "source": {
+ Help: "The source of the file",
+ Type: "string",
+ Example: "original",
+ },
+ "mtime": {
+ Help: "Time of last modification, managed by Rclone",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z",
+ },
+ "size": {
+ Help: "File size in bytes",
+ Type: "decimal number",
+ Example: "123456",
+ },
+ "md5": {
+ Help: "MD5 hash calculated by Internet Archive",
+ Type: "string",
+ Example: "01234567012345670123456701234567",
+ },
+ "crc32": {
+ Help: "CRC32 calculated by Internet Archive",
+ Type: "string",
+ Example: "01234567",
+ },
+ "sha1": {
+ Help: "SHA1 hash calculated by Internet Archive",
+ Type: "string",
+ Example: "0123456701234567012345670123456701234567",
+ },
+ "format": {
+ Help: "Name of format identified by Internet Archive",
+ Type: "string",
+ Example: "Comma-Separated Values",
+ },
+ "old_version": {
+ Help: "Whether the file was replaced and moved by keep-old-version flag",
+ Type: "boolean",
+ Example: "true",
+ },
+ "viruscheck": {
+ Help: "The last time viruscheck process was run for the file (?)",
+ Type: "unixtime",
+ Example: "1654191352",
+ },
+
+ "rclone-ia-mtime": {
+ Help: "Time of last modification, managed by Internet Archive",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z",
+ },
+ "rclone-mtime": {
+ Help: "Time of last modification, managed by Rclone",
+ Type: "RFC 3339",
+ Example: "2006-01-02T15:04:05.999999999Z",
+ },
+ "rclone-update-track": {
+ Help: "Random value used by Rclone for tracking changes inside Internet Archive",
+ Type: "string",
+ Example: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ },
+ },
+ Help: `Metadata fields provided by Internet Archive.
+If there are multiple values for a key, only the first one is returned.
+This is a limitation of Rclone, that supports one value per one key.
+
+Owner is able to add custom keys. Metadata feature grabs all the keys including them.
+`,
+ },
+
Options: []fs.Option{{
Name: "access_key_id",
Help: "IAS3 Access Key.\n\nLeave blank for anonymous access.\nYou can find one here: https://archive.org/account/s3.php",
@@ -90,6 +168,14 @@ Only enable if you need to be guaranteed to be reflected after write operations.
// maximum size of an item. this is constant across all items
const iaItemMaxSize int64 = 1099511627776
+// metadata keys that are not writeable
+var roMetadataKey = map[string]interface{}{
+ // do not add mtime here, it's a documented exception
+ "name": nil, "source": nil, "size": nil, "md5": nil,
+ "crc32": nil, "sha1": nil, "format": nil, "old_version": nil,
+ "viruscheck": nil,
+}
+
// Options defines the configuration for this backend
type Options struct {
AccessKeyID string `config:"access_key_id"`
@@ -122,6 +208,7 @@ type Object struct {
md5 string // md5 hash of the file presented by the server
sha1 string // sha1 hash of the file presented by the server
crc32 string // crc32 of the file presented by the server
+ rawData json.RawMessage
}
// IAFile reprensents a subset of object in MetadataResponse.Files
@@ -135,6 +222,8 @@ type IAFile struct {
Md5 string `json:"md5"`
Crc32 string `json:"crc32"`
Sha1 string `json:"sha1"`
+
+ rawData json.RawMessage
}
// MetadataResponse reprensents subset of the JSON object returned by (frontend)/metadata/
@@ -143,6 +232,12 @@ type MetadataResponse struct {
ItemSize int64 `json:"item_size"`
}
+// MetadataResponseRaw is the form of MetadataResponse to deal with metadata
+type MetadataResponseRaw struct {
+ Files []json.RawMessage `json:"files"`
+ ItemSize int64 `json:"item_size"`
+}
+
// ModMetadataResponse represents response for amending metadata
type ModMetadataResponse struct {
// https://archive.org/services/docs/api/md-write.html#example
@@ -226,7 +321,10 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
}
f.setRoot(root)
f.features = (&fs.Features{
- BucketBased: true,
+ BucketBased: true,
+ ReadMetadata: true,
+ WriteMetadata: true,
+ UserMetadata: true,
}).Fill(ctx, f)
f.srv = rest.NewClient(fshttp.NewClient(ctx))
@@ -307,18 +405,17 @@ func (o *Object) SetModTime(ctx context.Context, t time.Time) (err error) {
}
// https://archive.org/services/docs/api/md-write.html
- var patch = []interface{}{
+ // the following code might be useful for modifying metadata of an uploaded file
+ patch := []map[string]string{
// we should drop it first to clear all rclone-provided mtimes
- struct {
- Op string `json:"op"`
- Path string `json:"path"`
- }{"remove", "/rclone-mtime"},
- struct {
- Op string `json:"op"`
- Path string `json:"path"`
- Value string `json:"value"`
- }{"add", "/rclone-mtime", t.Format(time.RFC3339Nano)},
- }
+ {
+ "op": "remove",
+ "path": "/rclone-mtime",
+ }, {
+ "op": "add",
+ "path": "/rclone-mtime",
+ "value": t.Format(time.RFC3339Nano),
+ }}
res, err := json.Marshal(patch)
if err != nil {
return err
@@ -685,6 +782,23 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
headers["Content-Length"] = fmt.Sprintf("%d", size)
headers["x-archive-size-hint"] = fmt.Sprintf("%d", size)
}
+ var mdata fs.Metadata
+ mdata, err = fs.GetMetadataOptions(ctx, src, options)
+ if err == nil && mdata != nil {
+ for mk, mv := range mdata {
+ mk = strings.ToLower(mk)
+ if strings.HasPrefix(mk, "rclone-") {
+ fs.LogPrintf(fs.LogLevelWarning, o, "reserved metadata key %s is about to set", mk)
+ } else if _, ok := roMetadataKey[mk]; ok {
+ fs.LogPrintf(fs.LogLevelWarning, o, "setting or modifying read-only key %s is requested, skipping", mk)
+ continue
+ } else if mk == "mtime" {
+ // redirect to make it work
+ mk = "rclone-mtime"
+ }
+ headers[fmt.Sprintf("x-amz-filemeta-%s", mk)] = mv
+ }
+ }
// read the md5sum if available
var md5sumHex string
@@ -762,6 +876,34 @@ func (o *Object) String() string {
return o.remote
}
+// Metadata returns all file metadata provided by Internet Archive
+func (o *Object) Metadata(ctx context.Context) (m fs.Metadata, err error) {
+ if o.rawData == nil {
+ return nil, nil
+ }
+ raw := make(map[string]json.RawMessage)
+ err = json.Unmarshal(o.rawData, &raw)
+ if err != nil {
+ // fatal: json parsing failed
+ return
+ }
+ for k, v := range raw {
+ items, err := listOrString(v)
+ if len(items) == 0 || err != nil {
+ // skip: an entry failed to parse
+ continue
+ }
+ m.Set(k, items[0])
+ }
+ // move the old mtime to an another key
+ if v, ok := m["mtime"]; ok {
+ m["rclone-ia-mtime"] = v
+ }
+ // overwrite with a correct mtime
+ m["mtime"] = o.modTime.Format(time.RFC3339Nano)
+ return
+}
+
func (f *Fs) shouldRetry(resp *http.Response, err error) (bool, error) {
if resp != nil {
for _, e := range retryErrorCodes {
@@ -788,7 +930,7 @@ func (o *Object) split() (bucket, bucketPath string) {
return o.fs.split(o.remote)
}
-func (f *Fs) requestMetadata(ctx context.Context, bucket string) (result MetadataResponse, err error) {
+func (f *Fs) requestMetadata(ctx context.Context, bucket string) (result *MetadataResponse, err error) {
var resp *http.Response
// make a GET request to (frontend)/metadata/:item/
opts := rest.Opts{
@@ -796,12 +938,15 @@ func (f *Fs) requestMetadata(ctx context.Context, bucket string) (result Metadat
Path: path.Join("/metadata/", bucket),
}
+ var temp MetadataResponseRaw
err = f.pacer.Call(func() (bool, error) {
- resp, err = f.front.CallJSON(ctx, &opts, nil, &result)
+ resp, err = f.front.CallJSON(ctx, &opts, nil, &temp)
return f.shouldRetry(resp, err)
})
-
- return result, err
+ if err != nil {
+ return
+ }
+ return temp.unraw()
}
// list up all files/directories without any filters
@@ -998,6 +1143,7 @@ func makeValidObject(f *Fs, remote string, file IAFile, mtime time.Time, size in
md5: file.Md5,
crc32: file.Crc32,
sha1: file.Sha1,
+ rawData: file.rawData,
}
}
@@ -1045,6 +1191,23 @@ func (file IAFile) parseMtime() (mtime time.Time) {
return mtime
}
+func (mrr *MetadataResponseRaw) unraw() (_ *MetadataResponse, err error) {
+ var files []IAFile
+ for _, raw := range mrr.Files {
+ var parsed IAFile
+ err = json.Unmarshal(raw, &parsed)
+ if err != nil {
+ return nil, err
+ }
+ parsed.rawData = raw
+ files = append(files, parsed)
+ }
+ return &MetadataResponse{
+ Files: files,
+ ItemSize: mrr.ItemSize,
+ }, nil
+}
+
func compareSize(a, b int64) bool {
if a < 0 || b < 0 {
// we won't compare if any of them is not known
@@ -1106,4 +1269,5 @@ var (
_ fs.PublicLinker = &Fs{}
_ fs.Abouter = &Fs{}
_ fs.Object = &Object{}
+ _ fs.Metadataer = &Object{}
)
diff --git a/docs/content/internetarchive.md b/docs/content/internetarchive.md
index d0e937fdfaa2e..622db4d608bf3 100644
--- a/docs/content/internetarchive.md
+++ b/docs/content/internetarchive.md
@@ -38,6 +38,33 @@ You can optionally wait for the server's processing to finish, by setting non-ze
By making it wait, rclone can do normal file comparison.
Make sure to set a large enough value (e.g. `30m0s` for smaller files) as it can take a long time depending on server's queue.
+## About metadata
+This backend supports setting, updating and reading metadata of each file.
+The metadata will appear as file metadata on Internet Archive.
+However, some fields are reserved by both Internet Archive and rclone.
+
+The following are reserved by Internet Archive:
+- `name`
+- `source`
+- `size`
+- `md5`
+- `crc32`
+- `sha1`
+- `format`
+- `old_version`
+- `viruscheck`
+
+Trying to set values to these keys is ignored with a warning.
+Only setting `mtime` is an exception. Doing so make it the identical behavior as setting ModTime.
+
+rclone reserves all the keys starting with `rclone-`. Setting value for these keys will give you warnings, but values are set according to request.
+
+If there are multiple values for a key, only the first one is returned.
+This is a limitation of rclone, that supports one value per one key.
+It can be triggered when you did a server-side copy.
+
+Reading metadata will also provide custom (non-standard nor reserved) ones.
+
## Configuration
Here is an example of making an internetarchive configuration.
diff --git a/docs/content/overview.md b/docs/content/overview.md
index 3f298c1ba34b6..2a9301acf45dc 100644
--- a/docs/content/overview.md
+++ b/docs/content/overview.md
@@ -33,7 +33,7 @@ Here is an overview of the major features of each cloud storage system.
| HiDrive | HiDrive ¹² | R/W | No | No | - | - |
| HTTP | - | R | No | No | R | - |
| Hubic | MD5 | R/W | No | No | R/W | - |
-| Internet Archive | MD5, SHA1, CRC32 | R/W ¹¹ | No | No | - | - |
+| Internet Archive | MD5, SHA1, CRC32 | R/W ¹¹ | No | No | - | RWU |
| Jottacloud | MD5 | R/W | Yes | No | R | - |
| Koofr | MD5 | - | Yes | No | - | - |
| Mail.ru Cloud | Mailru ⁶ | R/W | Yes | No | - | - |
From 6e9c1eebd96e0c25b4814d58c60da06b4a3cc74b Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sat, 9 Jul 2022 12:35:59 +0100
Subject: [PATCH 169/560] Add Claudio Maradonna to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index d4129e3756f09..ed7c36d03998c 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -624,3 +624,4 @@ put them back in again.` >}}
* zzr93 <34027824+zzr93@users.noreply.github.com>
* Paul Norman
* Lorenzo Maiorfi
+ * Claudio Maradonna
From 876f12f2c48abf446fbd73a086785c01d2495cd4 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sat, 9 Jul 2022 12:35:59 +0100
Subject: [PATCH 170/560] Add Ovidiu Victor Tatar to contributors
---
docs/content/authors.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/content/authors.md b/docs/content/authors.md
index ed7c36d03998c..0e8e998691095 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -625,3 +625,4 @@ put them back in again.` >}}
* Paul Norman
* Lorenzo Maiorfi
* Claudio Maradonna
+ * Ovidiu Victor Tatar
From 1c4ee2feee5c954e5d791f954124cbe26e8fe3f7 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sat, 9 Jul 2022 17:31:12 +0100
Subject: [PATCH 171/560] gcs: add --gcs-decompress flag to download
gzip-encoded files
By default these will be downloaded compressed.
This changes the default of the previous commit
2781f8e2f14f146d gcs: Fix download of "Content-Encoding: gzip" compressed objects
But will fit in better with the metadata framework when copying
gzip-encoded objects from backend to backend.
---
.../googlecloudstorage/googlecloudstorage.go | 24 +++++++++----------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/backend/googlecloudstorage/googlecloudstorage.go b/backend/googlecloudstorage/googlecloudstorage.go
index b11771d1b8351..b4d355021b053 100644
--- a/backend/googlecloudstorage/googlecloudstorage.go
+++ b/backend/googlecloudstorage/googlecloudstorage.go
@@ -307,17 +307,15 @@ rclone does if you know the bucket exists already.
Default: false,
Advanced: true,
}, {
- Name: "download_compressed",
- Help: `If set this will download compressed objects as-is.
+ Name: "decompress",
+ Help: `If set this will decompress gzip encoded objects.
It is possible to upload objects to GCS with "Content-Encoding: gzip"
-set. Normally rclone will transparently decompress these files on
-download. This means that rclone can't check the hash or the size of
-the file as both of these refer to the compressed object.
+set. Normally rclone will download these files files as compressed objects.
-If this flag is set then rclone will download files with
+If this flag is set then rclone will decompress these files with
"Content-Encoding: gzip" as they are received. This means that rclone
-can check the size and hash but the file contents will be compressed.
+can't check the size and hash but the file contents will be decompressed.
`,
Advanced: true,
Default: false,
@@ -344,7 +342,7 @@ type Options struct {
Location string `config:"location"`
StorageClass string `config:"storage_class"`
NoCheckBucket bool `config:"no_check_bucket"`
- DownloadCompressed bool `config:"download_compressed"`
+ Decompress bool `config:"decompress"`
Enc encoder.MultiEncoder `config:"encoding"`
}
@@ -1036,12 +1034,9 @@ func (o *Object) setMetaData(info *storage.Object) {
}
// If gunzipping then size and md5sum are unknown
- if o.gzipped && !o.fs.opt.DownloadCompressed {
+ if o.gzipped && o.fs.opt.Decompress {
o.bytes = -1
o.md5sum = ""
- o.fs.warnCompressed.Do(func() {
- fs.Logf(o.fs, "Decompressing 'Content-Encoding: gzip' compressed file. Use --gcs-download-compressed to override")
- })
}
}
@@ -1143,7 +1138,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
return nil, err
}
fs.FixRangeOption(options, o.bytes)
- if o.gzipped && o.fs.opt.DownloadCompressed {
+ if o.gzipped && !o.fs.opt.Decompress {
// Allow files which are stored on the cloud storage system
// compressed to be downloaded without being decompressed. Note
// that setting this here overrides the automatic decompression
@@ -1151,6 +1146,9 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
//
// See: https://cloud.google.com/storage/docs/transcoding
req.Header.Set("Accept-Encoding", "gzip")
+ o.fs.warnCompressed.Do(func() {
+ fs.Logf(o, "Not decompressing 'Content-Encoding: gzip' compressed file. Use --gcs-decompress to override")
+ })
}
fs.OpenOptionAddHTTPHeaders(req.Header, options)
var res *http.Response
From 00a684d877548b47990a96cef8eeb1462a590c2a Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood
Date: Sat, 9 Jul 2022 18:08:20 +0100
Subject: [PATCH 172/560] Version v1.59.0
---
MANUAL.html | 5392 ++++++++---
MANUAL.md | 5225 +++++++++--
MANUAL.txt | 5276 +++++++++--
docs/content/alias.md | 2 +-
docs/content/amazonclouddrive.md | 4 +-
docs/content/azureblob.md | 4 +-
docs/content/b2.md | 18 +-
docs/content/box.md | 4 +-
docs/content/cache.md | 6 +-
docs/content/changelog.md | 159 +
docs/content/chunker.md | 4 +-
docs/content/combine.md | 8 +-
docs/content/commands/rclone.md | 2 +-
docs/content/commands/rclone_check.md | 4 +
docs/content/commands/rclone_completion.md | 13 +-
.../commands/rclone_completion_bash.md | 24 +-
.../commands/rclone_completion_fish.md | 13 +-
.../commands/rclone_completion_powershell.md | 10 +-
.../content/commands/rclone_completion_zsh.md | 21 +-
docs/content/commands/rclone_copy.md | 9 +-
docs/content/commands/rclone_copyto.md | 4 +-
docs/content/commands/rclone_copyurl.md | 20 +-
docs/content/commands/rclone_cryptcheck.md | 6 +-
docs/content/commands/rclone_cryptdecode.md | 6 +-
docs/content/commands/rclone_dedupe.md | 2 +-
docs/content/commands/rclone_delete.md | 10 +-
.../commands/rclone_genautocomplete.md | 2 +-
docs/content/commands/rclone_hashsum.md | 4 +
docs/content/commands/rclone_listremotes.md | 2 +-
docs/content/commands/rclone_lsd.md | 4 +-
docs/content/commands/rclone_lsf.md | 13 +-
docs/content/commands/rclone_lsjson.md | 29 +-
docs/content/commands/rclone_md5sum.md | 4 +
docs/content/commands/rclone_mount.md | 67 +-
docs/content/commands/rclone_move.md | 6 +-
docs/content/commands/rclone_moveto.md | 2 +-
docs/content/commands/rclone_ncdu.md | 31 +-
docs/content/commands/rclone_obscure.md | 2 +-
docs/content/commands/rclone_purge.md | 7 +-
docs/content/commands/rclone_rc.md | 20 +-
docs/content/commands/rclone_rcat.md | 6 +-
docs/content/commands/rclone_rmdir.md | 6 +-
docs/content/commands/rclone_rmdirs.md | 13 +-
docs/content/commands/rclone_serve.md | 6 +-
docs/content/commands/rclone_serve_dlna.md | 77 +-
docs/content/commands/rclone_serve_docker.md | 61 +-
docs/content/commands/rclone_serve_ftp.md | 67 +-
docs/content/commands/rclone_serve_http.md | 115 +-
docs/content/commands/rclone_serve_restic.md | 58 +-
docs/content/commands/rclone_serve_sftp.md | 95 +-
docs/content/commands/rclone_serve_webdav.md | 124 +-
docs/content/commands/rclone_sha1sum.md | 4 +
docs/content/commands/rclone_size.md | 22 +
docs/content/commands/rclone_sync.md | 6 +-
docs/content/commands/rclone_test.md | 1 +
docs/content/commands/rclone_test_makefile.md | 33 +
.../content/commands/rclone_test_makefiles.md | 5 +
docs/content/commands/rclone_tree.md | 8 +-
docs/content/compress.md | 10 +-
docs/content/crypt.md | 12 +-
docs/content/drive.md | 91 +-
docs/content/dropbox.md | 4 +-
docs/content/fichier.md | 4 +-
docs/content/filefabric.md | 4 +-
docs/content/flags.md | 51 +-
docs/content/ftp.md | 4 +-
docs/content/googlecloudstorage.md | 38 +-
docs/content/googlephotos.md | 4 +-
docs/content/hasher.md | 12 +-
docs/content/hdfs.md | 4 +-
docs/content/hidrive.md | 23 +-
docs/content/http.md | 6 +-
docs/content/hubic.md | 4 +-
docs/content/internetarchive.md | 32 +-
docs/content/jottacloud.md | 2 +-
docs/content/koofr.md | 4 +-
docs/content/local.md | 9 +-
docs/content/mailru.md | 4 +-
docs/content/mega.md | 4 +-
docs/content/netstorage.md | 12 +-
docs/content/onedrive.md | 26 +-
docs/content/opendrive.md | 4 +-
docs/content/pcloud.md | 4 +-
docs/content/premiumizeme.md | 4 +-
docs/content/putio.md | 2 +-
docs/content/qingstor.md | 4 +-
docs/content/rc.md | 152 +-
docs/content/s3.md | 310 +-
docs/content/seafile.md | 4 +-
docs/content/sftp.md | 103 +-
docs/content/sharefile.md | 4 +-
docs/content/sia.md | 4 +-
docs/content/storj.md | 2 +-
docs/content/sugarsync.md | 4 +-
docs/content/swift.md | 4 +-
docs/content/union.md | 26 +-
docs/content/uptobox.md | 4 +-
docs/content/webdav.md | 6 +-
docs/content/yandex.md | 4 +-
docs/content/zoho.md | 8 +-
rclone.1 | 8019 ++++++++++++++---
101 files changed, 21980 insertions(+), 4202 deletions(-)
create mode 100644 docs/content/commands/rclone_test_makefile.md
diff --git a/MANUAL.html b/MANUAL.html
index eb2a7d1970566..4c0d99e6195f2 100644
--- a/MANUAL.html
+++ b/MANUAL.html
@@ -13,75 +13,13 @@
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
- pre > code.sourceCode { white-space: pre; position: relative; }
- pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
- pre > code.sourceCode > span:empty { height: 1.2em; }
- code.sourceCode > span { color: inherit; text-decoration: inherit; }
- div.sourceCode { margin: 1em 0; }
- pre.sourceCode { margin: 0; }
- @media screen {
- div.sourceCode { overflow: auto; }
- }
- @media print {
- pre > code.sourceCode { white-space: pre-wrap; }
- pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
- }
- pre.numberSource code
- { counter-reset: source-line 0; }
- pre.numberSource code > span
- { position: relative; left: -4em; counter-increment: source-line; }
- pre.numberSource code > span > a:first-child::before
- { content: counter(source-line);
- position: relative; left: -1em; text-align: right; vertical-align: baseline;
- border: none; display: inline-block;
- -webkit-touch-callout: none; -webkit-user-select: none;
- -khtml-user-select: none; -moz-user-select: none;
- -ms-user-select: none; user-select: none;
- padding: 0 4px; width: 4em;
- color: #aaaaaa;
- }
- pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
- div.sourceCode
- { }
- @media screen {
- pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
- }
- code span.al { color: #ff0000; font-weight: bold; } /* Alert */
- code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
- code span.at { color: #7d9029; } /* Attribute */
- code span.bn { color: #40a070; } /* BaseN */
- code span.bu { } /* BuiltIn */
- code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
- code span.ch { color: #4070a0; } /* Char */
- code span.cn { color: #880000; } /* Constant */
- code span.co { color: #60a0b0; font-style: italic; } /* Comment */
- code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
- code span.do { color: #ba2121; font-style: italic; } /* Documentation */
- code span.dt { color: #902000; } /* DataType */
- code span.dv { color: #40a070; } /* DecVal */
- code span.er { color: #ff0000; font-weight: bold; } /* Error */
- code span.ex { } /* Extension */
- code span.fl { color: #40a070; } /* Float */
- code span.fu { color: #06287e; } /* Function */
- code span.im { } /* Import */
- code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
- code span.kw { color: #007020; font-weight: bold; } /* Keyword */
- code span.op { color: #666666; } /* Operator */
- code span.ot { color: #007020; } /* Other */
- code span.pp { color: #bc7a00; } /* Preprocessor */
- code span.sc { color: #4070a0; } /* SpecialChar */
- code span.ss { color: #bb6688; } /* SpecialString */
- code span.st { color: #4070a0; } /* String */
- code span.va { color: #19177c; } /* Variable */
- code span.vs { color: #4070a0; } /* VerbatimString */
- code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
Rclone syncs your files to cloud storage

@@ -130,7 +68,7 @@ Features
Move files to cloud storage deleting the local after verification
Check hashes and for missing/extra files
Mount your cloud storage as a network disk
-Serve local or remote files over HTTP/WebDav/FTP/SFTP/dlna
+Serve local or remote files over HTTP/WebDav/FTP/SFTP/DLNA
Experimental Web based GUI
Supported providers
@@ -144,8 +82,11 @@ Supported providers
Backblaze B2
Box
Ceph
+China Mobile Ecloud Elastic Object Storage (EOS)
+Arvan Cloud Object Storage (AOS)
Citrix ShareFile
C14
+Cloudflare R2
DigitalOcean Spaces
Digi Storage
Dreamhost
@@ -156,10 +97,14 @@ Supported providers
Google Drive
Google Photos
HDFS
+Hetzner Storage Box
+HiDrive
HTTP
Hubic
+Internet Archive
Jottacloud
IBM COS S3
+IDrive e2
Koofr
Mail.ru Cloud
Memset Memstore
@@ -197,7 +142,19 @@ Supported providers
Zoho WorkDrive
The local filesystem
-Links
+Virtual providers
+These backends adapt or modify other storage providers:
+
+- Alias: Rename existing remotes
+- Cache: Cache remotes (DEPRECATED)
+- Chunker: Split large files
+- Combine: Combine multiple remotes into a directory tree
+- Compress: Compress files
+- Crypt: Encrypt files
+- Hasher: Hash files
+- Union: Join multiple remotes to work together
+
+Links
curl https://rclone.org/install.sh | sudo bash
+sudo -v ; curl https://rclone.org/install.sh | sudo bash
For beta installation, run:
-curl https://rclone.org/install.sh | sudo bash -s beta
+sudo -v ; curl https://rclone.org/install.sh | sudo bash -s beta
Note that this script checks the version of rclone installed first and won't re-download if not needed.
Linux installation from precompiled binary
Fetch and unpack
@@ -256,7 +213,7 @@ macOS installatio
rclone config
macOS installation from precompiled binary, using a web browser
When downloading a binary with a web browser, the browser will set the macOS gatekeeper quarantine attribute. Starting from Catalina, when attempting to run rclone, a pop-up will appear saying:
-“rclone” cannot be opened because the developer cannot be verified.
+"rclone" cannot be opened because the developer cannot be verified.
macOS cannot verify that this app is free from malware.
The simplest fix is to run
xattr -d com.apple.quarantine rclone
@@ -308,18 +265,26 @@ Install with docker
ls ~/data/mount
kill %1
Install from source
-Make sure you have at least Go go1.15 installed. Download go if necessary. The latest release is recommended. Then
-git clone https://github.com/rclone/rclone.git
-cd rclone
-go build
-# If on macOS and mount is wanted, instead run: make GOTAGS=cmount
-./rclone version
-This will leave you a checked out version of rclone you can modify and send pull requests with. If you use make instead of go build then the rclone build will have the correct version information in it.
-You can also build the latest stable rclone with:
+Make sure you have git and Go installed. Go version 1.16 or newer is required, latest release is recommended. You can get it from your package manager, or download it from golang.org/dl. Then you can run the following:
+git clone https://github.com/rclone/rclone.git
+cd rclone
+go build
+This will check out the rclone source in subfolder rclone, which you can later modify and send pull requests with. Then it will build the rclone executable in the same folder. As an initial check you can now run ./rclone version (.\rclone version on Windows).
+Note that on macOS and Windows the mount command will not be available unless you specify additional build tag cmount.
+go build -tags cmount
+This assumes you have a GCC compatible C compiler (GCC or Clang) in your PATH, as it uses cgo. But on Windows, the cgofuse library that the cmount implementation is based on, also supports building without cgo, i.e. by setting environment variable CGO_ENABLED to value 0 (static linking). This is how the official Windows release of rclone is being built, starting with version 1.59. It is still possible to build with cgo on Windows as well, by using the MinGW port of GCC, e.g. by installing it in a MSYS2 distribution (make sure you install it in the classic mingw64 subsystem, the ucrt64 version is not compatible).
+Additionally, on Windows, you must install the third party utility WinFsp, with the "Developer" feature selected. If building with cgo, you must also set environment variable CPATH pointing to the fuse include directory within the WinFsp installation (normally C:\Program Files (x86)\WinFsp\inc\fuse).
+You may also add arguments -ldflags -s (with or without -tags cmount), to omit symbol table and debug information, making the executable file smaller, and -trimpath to remove references to local file system paths. This is how the official rclone releases are built.
+go build -trimpath -ldflags -s -tags cmount
+Instead of executing the go build command directly, you can run it via the Makefile, which also sets version information and copies the resulting rclone executable into your GOPATH bin folder ($(go env GOPATH)/bin, which corresponds to ~/go/bin/rclone by default).
+make
+To include mount command on macOS and Windows with Makefile build:
+make GOTAGS=cmount
+As an alternative you can download the source, build and install rclone in one operation, as a regular Go package. The source will be stored it in the Go module cache, and the resulting executable will be in your GOPATH bin folder ($(go env GOPATH)/bin, which corresponds to ~/go/bin/rclone by default).
+With Go version 1.17 or newer:
+go install github.com/rclone/rclone@latest
+With Go versions older than 1.17 (do not use the -u flag, it causes Go to try to update the dependencies that rclone uses and sometimes these don't work with the current version):
go get github.com/rclone/rclone
-or the latest version (equivalent to the beta) with
-go get github.com/rclone/rclone@master
-These will build the binary in $(go env GOPATH)/bin (~/go/bin/rclone by default) after downloading the source to the go module cache. Note - do not use the -u flag here. This causes go to try to update the dependencies that rclone uses and sometimes these don't work with the current version of rclone.
Installation with Ansible
This can be done with Stefan Weichinger's ansible role.
Instructions
@@ -354,7 +319,7 @@ Start from Task Scheduler
Run as service
For running rclone at system startup, you can create a Windows service that executes your rclone command, as an alternative to scheduled task configured to run at startup.
Mount command built-in service integration
-For mount commands, Rclone has a built-in Windows service integration via the third-party WinFsp library it uses. Registering as a regular Windows service easy, as you just have to execute the built-in PowerShell command New-Service (requires administrative privileges).
+For mount commands, rclone has a built-in Windows service integration via the third-party WinFsp library it uses. Registering as a regular Windows service easy, as you just have to execute the built-in PowerShell command New-Service (requires administrative privileges).
Example of a PowerShell command that creates a Windows service for mounting some remote:/files as drive letter X:, for all users (service will be running as the local system account):
New-Service -Name Rclone -BinaryPathName 'c:\rclone\rclone.exe mount remote:/files X: --config c:\rclone\config\rclone.conf --log-file c:\rclone\logs\mount.txt'
The WinFsp service infrastructure supports incorporating services for file system implementations, such as rclone, into its own launcher service, as kind of "child services". This has the additional advantage that it also implements a network provider that integrates into Windows standard methods for managing network drives. This is currently not officially supported by Rclone, but with WinFsp version 2019.3 B2 / v1.5B2 or later it should be possible through path rewriting as described here.
@@ -384,6 +349,7 @@
Chunker - transparently splits large files for other remotes
Citrix ShareFile
Compress
+Combine
Crypt - to encrypt other remotes
DigitalOcean Spaces
Digi Storage
@@ -395,8 +361,10 @@
Google Photos
Hasher - to handle checksums for other remotes
HDFS
+HiDrive
HTTP
Hubic
+Internet Archive
Jottacloud
Koofr
Mail.ru Cloud
@@ -462,8 +430,9 @@ SEE ALSO
rclone copy
Copy files from source to dest, skipping identical files.
Synopsis
-Copy the source to the destination. Does not transfer files that are identical on source and destination, testing by size and modification time or MD5SUM. Doesn't delete files from the destination.
-Note that it is always the contents of the directory that is synced, not the directory so when source:path is a directory, it's the contents of source:path that are copied, not the directory name and contents.
+Copy the source to the destination. Does not transfer files that are identical on source and destination, testing by size and modification time or MD5SUM. Doesn't delete files from the destination. If you want to also delete files from destination, to make it match source, use the sync command instead.
+Note that it is always the contents of the directory that is synced, not the directory itself. So when source:path is a directory, it's the contents of source:path that are copied, not the directory name and contents.
+To copy single files, use the copyto command instead.
If dest:path doesn't exist, it is created and the source:path contents go there.
For example
rclone copy source:sourcepath dest:destpath
@@ -494,11 +463,11 @@ SEE ALSO
rclone sync
Make source and dest identical, modifying destination only.
Synopsis
-Sync the source to the destination, changing the destination only. Doesn't transfer files that are identical on source and destination, testing by size and modification time or MD5SUM. Destination is updated to match source, including deleting files if necessary (except duplicate objects, see below).
+Sync the source to the destination, changing the destination only. Doesn't transfer files that are identical on source and destination, testing by size and modification time or MD5SUM. Destination is updated to match source, including deleting files if necessary (except duplicate objects, see below). If you don't want to delete files from destination, use the copy command instead.
Important: Since this can cause data loss, test first with the --dry-run or the --interactive/-i flag.
rclone sync -i SOURCE remote:DESTINATION
Note that files in the destination won't be deleted if there were any errors at any point. Duplicate objects (files with the same name, on those providers that support it) are also not yet handled.
-It is always the contents of the directory that is synced, not the directory so when source:path is a directory, it's the contents of source:path that are copied, not the directory name and contents. See extended explanation in the copy command above if unsure.
+It is always the contents of the directory that is synced, not the directory itself. So when source:path is a directory, it's the contents of source:path that are copied, not the directory name and contents. See extended explanation in the copy command if unsure.
If dest:path doesn't exist, it is created and the source:path contents go there.
Note: Use the -P/--progress flag to view real-time transfer statistics
Note: Use the rclone dedupe command to deal with "Duplicate object/directory found in source/destination - ignoring" errors. See this forum post for more info.
@@ -515,9 +484,10 @@ rclone move
Move files from source to dest.
Synopsis
Moves the contents of the source directory to the destination directory. Rclone will error if the source and destination overlap and the remote does not support a server-side directory move operation.
+To move single files, use the moveto command instead.
If no filters are in use and if possible this will server-side move source:path into dest:path. After this source:path will no longer exist.
Otherwise for each file in source:path selected by the filters (if any) this will move it into dest:path. If possible a server-side move will be used, otherwise it will copy it (server-side if possible) into dest:path then delete the original (if no errors on copy) in source:path.
-If you want to delete empty source directories after move, use the --delete-empty-src-dirs flag.
+If you want to delete empty source directories after move, use the --delete-empty-src-dirs flag.
See the --no-traverse option for controlling whether rclone lists the destination directory or not. Supplying this option when moving a small number of files into a large destination can speed transfers up greatly.
Important: Since this can cause data loss, test first with the --dry-run or the --interactive/-i flag.
Note: Use the -P/--progress flag to view real-time transfer statistics.
@@ -534,9 +504,9 @@ SEE ALSO
rclone delete
Remove the files in path.
Synopsis
-Remove the files in path. Unlike purge it obeys include/exclude filters so can be used to selectively delete files.
-rclone delete only deletes files but leaves the directory structure alone. If you want to delete a directory and all of its contents use the purge command.
-If you supply the --rmdirs flag, it will remove all empty directories along with it. You can also use the separate command rmdir or rmdirs to delete empty directories only.
+Remove the files in path. Unlike purge it obeys include/exclude filters so can be used to selectively delete files.
+rclone delete only deletes files but leaves the directory structure alone. If you want to delete a directory and all of its contents use the purge command.
+If you supply the --rmdirs flag, it will remove all empty directories along with it. You can also use the separate command rmdir or rmdirs to delete empty directories only.
For example, to delete all files bigger than 100 MiB, you may first want to check what would be deleted (use either):
rclone --min-size 100M lsl remote:path
rclone --dry-run --min-size 100M delete remote:path
@@ -556,7 +526,7 @@ SEE ALSO
rclone purge
Remove the path and all of its contents.
Synopsis
-Remove the path and all of its contents. Note that this does not obey include/exclude filters - everything will be removed. Use the delete command if you want to selectively delete files. To delete empty directories only, use command rmdir or rmdirs.
+Remove the path and all of its contents. Note that this does not obey include/exclude filters - everything will be removed. Use the delete command if you want to selectively delete files. To delete empty directories only, use command rmdir or rmdirs.
Important: Since this can cause data loss, test first with the --dry-run or the --interactive/-i flag.
rclone purge remote:path [flags]
Options
@@ -579,8 +549,8 @@ SEE ALSO
rclone rmdir
Remove the empty directory at path.
Synopsis
-This removes empty directory given by path. Will not remove the path if it has any objects in it, not even empty subdirectories. Use command rmdirs (or delete with option --rmdirs) to do that.
-To delete a path and any objects in it, use purge command.
+This removes empty directory given by path. Will not remove the path if it has any objects in it, not even empty subdirectories. Use command rmdirs (or delete with option --rmdirs) to do that.
+To delete a path and any objects in it, use purge command.
rclone rmdir remote:path [flags]
Options
-h, --help help for rmdir
@@ -593,6 +563,7 @@ rclone check
Checks the files in the source and destination match.
Synopsis
Checks the files in the source and destination match. It compares sizes and hashes (MD5 or SHA1) and logs a report of files that don't match. It doesn't alter the source or destination.
+For the crypt remote there is a dedicated command, cryptcheck, that are able to check the checksums of the crypted files.
If you supply the --size-only flag, it will only compare the sizes not the hashes as well. Use this for a quick check.
If you supply the --download flag, it will download the data from both remotes and check them against each other on the fly. This can be useful for remotes that don't support hashes or if you really want to check all the data.
If you supply the --checkfile HASH flag with a valid hash name, the source:path must point to a text file in the SUM format.
@@ -657,7 +628,7 @@ SEE ALSO
rclone lsd
List all directories/containers/buckets in the path.
Synopsis
-Lists the directories in the source path to standard output. Does not recurse by default. Use the -R flag to recurse.
+Lists the directories in the source path to standard output. Does not recurse by default. Use the -R flag to recurse.
This command lists the total size of the directory (if known, -1 if not), the modification time (if known, the current time if not), the number of objects in the directory (if known, -1 if not) and the name of the directory, Eg
$ rclone lsd swift:
494000 2018-04-26 08:43:20 10000 10000files
@@ -667,7 +638,7 @@ Synopsis
-1 2016-10-17 17:41:53 -1 1000files
-1 2017-01-03 14:40:54 -1 2500files
-1 2017-07-08 14:39:28 -1 4000files
-If you just want the directory names use "rclone lsf --dirs-only".
+If you just want the directory names use rclone lsf --dirs-only.
Any of the filtering options can be applied to this command.
There are several related list commands
@@ -726,6 +697,7 @@ rclone md5sum
Synopsis
Produces an md5sum file for all the objects in the path. This is in the same format as the standard md5sum tool produces.
By default, the hash is requested from the remote. If MD5 is not supported by the remote, no hash will be returned. With the download flag, the file will be downloaded from the remote and hashed locally enabling MD5 for any remote.
+For other algorithms, see the hashsum command. Running rclone md5sum remote:path is equivalent to running rclone hashsum MD5 remote:path.
This command can also hash data received on standard input (stdin), by not passing a remote:path, or by passing a hyphen as remote:path when there is data to read (if not, the hypen will be treated literaly, as a relative path).
rclone md5sum remote:path [flags]
Options
@@ -744,6 +716,7 @@ rclone sha1sum
Synopsis
Produces an sha1sum file for all the objects in the path. This is in the same format as the standard sha1sum tool produces.
By default, the hash is requested from the remote. If SHA-1 is not supported by the remote, no hash will be returned. With the download flag, the file will be downloaded from the remote and hashed locally enabling SHA-1 for any remote.
+For other algorithms, see the hashsum command. Running rclone sha1sum remote:path is equivalent to running rclone hashsum SHA1 remote:path.
This command can also hash data received on standard input (stdin), by not passing a remote:path, or by passing a hyphen as remote:path when there is data to read (if not, the hypen will be treated literaly, as a relative path).
This command can also hash data received on STDIN, if not passing a remote:path.
rclone sha1sum remote:path [flags]
@@ -760,6 +733,11 @@ SEE ALSO
rclone size
Prints the total size and number of objects in remote:path.
+Synopsis
+Counts objects in the path and calculates the total size. Prints the result to standard output.
+By default the output is in human-readable format, but shows values in both human-readable format as well as the raw numbers (global option --human-readable is not considered). Use option --json to format output as JSON instead.
+Recurses by default, use --max-depth 1 to stop the recursion.
+Some backends do not always provide file sizes, see for example Google Photos and Google Drive. Rclone will then show a notice in the log indicating how many such files were encountered, and count them in as empty files in the output of the size command.
rclone size remote:path [flags]
Options
-h, --help help for size
@@ -771,7 +749,7 @@ SEE ALSO
rclone version
Show the version number.
-Synopsis
+Synopsis
Show the rclone version number, the go version, the build target OS and architecture, the runtime OS and kernel version and bitness, build tags and the type of executable (static or dynamic).
For example:
$ rclone version
@@ -807,7 +785,7 @@ SEE ALSO
rclone cleanup
Clean up the remote if possible.
-Synopsis
+Synopsis
Clean up the remote if possible. Empty the trash or delete old file versions. Not supported by all remotes.
rclone cleanup remote:path [flags]
Options
@@ -819,10 +797,10 @@ SEE ALSO
rclone dedupe
Interactively find duplicate filenames and delete/rename them.
-Synopsis
+Synopsis
By default dedupe interactively finds files with duplicate names and offers to delete all but one or rename them to be different. This is known as deduping by name.
Deduping by name is only useful with a small group of backends (e.g. Google Drive, Opendrive) that can have duplicate file names. It can be run on wrapping backends (e.g. crypt) if they wrap a backend which supports duplicate file names.
-However if --by-hash is passed in then dedupe will find files with duplicate hashes instead which will work on any backend which supports at least one hash. This can be used to find files with duplicate content. This is known as deduping by hash.
+However if --by-hash is passed in then dedupe will find files with duplicate hashes instead which will work on any backend which supports at least one hash. This can be used to find files with duplicate content. This is known as deduping by hash.
If deduping by name, first rclone will merge directories with the same name. It will do this iteratively until all the identically named directories have been merged.
Next, if deduping by name, for every group of duplicate file names / hashes, it will delete all but one identical file it finds without confirmation. This means that for most duplicated files the dedupe command will not be interactive.
dedupe considers files to be identical if they have the same file path and the same hash. If the backend does not support hashes (e.g. crypt wrapping Google Drive) then they will never be found to be identical. If you use the --size-only flag then files will be considered identical if they have the same size (any hash will be ignored). This can be useful on crypt backends which do not support hashes.
@@ -898,7 +876,7 @@ SEE ALSO
rclone about
Get quota information from the remote.
-Synopsis
+Synopsis
rclone about prints quota information about a remote to standard output. The output is typically used, free, quota and trash contents.
E.g. Typical output from rclone about remote: is:
Total: 17 GiB
@@ -944,7 +922,7 @@ SEE ALSO
rclone authorize
Remote authorization.
-Synopsis
+Synopsis
Remote authorization. Used to authorize a remote or headless rclone from a machine with a browser - use as instructed by rclone config.
Use the --auth-no-open-browser to prevent rclone to open auth link in default browser automatically.
rclone authorize [flags]
@@ -958,7 +936,7 @@ SEE ALSO
rclone backend
Run a backend-specific command.
-Synopsis
+Synopsis
This runs a backend-specific command. The commands themselves (except for "help" and "features") are defined by the backends and you should see the backend docs for definitions.
You can discover what commands a backend implements by using
rclone backend help remote:
@@ -982,7 +960,7 @@ SEE ALSO
rclone bisync
Perform bidirectonal synchronization between two paths.
-Synopsis
+Synopsis
Perform bidirectonal synchronization between two paths.
Bisync provides a bidirectional cloud sync solution in rclone. It retains the Path1 and Path2 filesystem listings from the prior run. On each successive run it will: - list files on Path1 and Path2, and check for changes on each side. Changes include New, Newer, Older, and Deleted files. - Propagate changes on Path1 to Path2, and vice-versa.
See full bisync description for details.
@@ -1006,7 +984,7 @@ SEE ALSO
rclone cat
Concatenates any files and sends them to stdout.
-Synopsis
+Synopsis
rclone cat sends any files to standard output.
You can use it like this to output a single file
rclone cat remote:path/to/file
@@ -1030,7 +1008,7 @@ SEE ALSO
rclone checksum
Checks the files in the source against a SUM file.
-Synopsis
+Synopsis
Checks that hashsums of source files match the SUM file. It compares hashes (MD5, SHA1, etc) and logs a report of files which don't match. It doesn't alter the file system.
If you supply the --download flag, it will download the data from remote and calculate the contents hash on the fly. This can be useful for remotes that don't support hashes or if you really want to check all the data.
Note that hash values in the SUM file are treated as case insensitive.
@@ -1061,8 +1039,8 @@ SEE ALSO
rclone - Show help for rclone commands, flags and backends.
rclone completion
-generate the autocompletion script for the specified shell
-Synopsis
+Generate the autocompletion script for the specified shell
+Synopsis
Generate the autocompletion script for rclone for the specified shell. See each sub-command's help for details on how to use the generated script.
Options
-h, --help help for completion
@@ -1070,18 +1048,23 @@ Options
SEE ALSO
rclone completion bash
-generate the autocompletion script for bash
-Synopsis
+Generate the autocompletion script for bash
+Synopsis
Generate the autocompletion script for the bash shell.
This script depends on the 'bash-completion' package. If it is not installed already, you can install it via your OS's package manager.
-To load completions in your current shell session: $ source <(rclone completion bash)
-To load completions for every new session, execute once: Linux: $ rclone completion bash > /etc/bash_completion.d/rclone MacOS: $ rclone completion bash > /usr/local/etc/bash_completion.d/rclone
+To load completions in your current shell session:
+source <(rclone completion bash)
+To load completions for every new session, execute once:
+Linux:
+rclone completion bash > /etc/bash_completion.d/rclone
+macOS:
+rclone completion bash > /usr/local/etc/bash_completion.d/rclone
You will need to start a new shell for this setup to take effect.
rclone completion bash
Options
@@ -1090,14 +1073,16 @@ Options
See the global flags page for global options not listed here.
SEE ALSO
rclone completion fish
-generate the autocompletion script for fish
-Synopsis
+Generate the autocompletion script for fish
+Synopsis
Generate the autocompletion script for the fish shell.
-To load completions in your current shell session: $ rclone completion fish | source
-To load completions for every new session, execute once: $ rclone completion fish > ~/.config/fish/completions/rclone.fish
+To load completions in your current shell session:
+rclone completion fish | source
+To load completions for every new session, execute once:
+rclone completion fish > ~/.config/fish/completions/rclone.fish
You will need to start a new shell for this setup to take effect.
rclone completion fish [flags]
Options
@@ -1106,13 +1091,14 @@ Options
See the global flags page for global options not listed here.
SEE ALSO
rclone completion powershell
-generate the autocompletion script for powershell
-Synopsis
+Generate the autocompletion script for powershell
+Synopsis
Generate the autocompletion script for powershell.
-To load completions in your current shell session: PS C:> rclone completion powershell | Out-String | Invoke-Expression
+To load completions in your current shell session:
+rclone completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command to your powershell profile.
rclone completion powershell [flags]
Options
@@ -1121,15 +1107,19 @@ Options
See the global flags page for global options not listed here.
SEE ALSO
rclone completion zsh
-generate the autocompletion script for zsh
-Synopsis
+Generate the autocompletion script for zsh
+Synopsis
Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need to enable it. You can execute the following once:
-$ echo "autoload -U compinit; compinit" >> ~/.zshrc
-To load completions for every new session, execute once: # Linux: $ rclone completion zsh > "${fpath[1]}/_rclone" # macOS: $ rclone completion zsh > /usr/local/share/zsh/site-functions/_rclone
+echo "autoload -U compinit; compinit" >> ~/.zshrc
+To load completions for every new session, execute once:
+Linux:
+rclone completion zsh > "${fpath[1]}/_rclone"
+macOS:
+rclone completion zsh > /usr/local/share/zsh/site-functions/_rclone
You will need to start a new shell for this setup to take effect.
rclone completion zsh [flags]
Options
@@ -1138,11 +1128,11 @@ Options
See the global flags page for global options not listed here.
SEE ALSO
rclone config create
Create a new remote with name, type and options.
-Synopsis
+Synopsis
Create a new remote of name with type and options. The options should be passed in pairs of key value or as key=value.
For example, to make a swift remote of name myremote using auto config you would do:
rclone config create myremote swift env_auth true
@@ -1223,7 +1213,7 @@ SEE ALSO
rclone config disconnect
Disconnects user from remote
-Synopsis
+Synopsis
This disconnects the remote: passed in to the cloud storage system.
This normally means revoking the oauth token.
To reconnect use "rclone config reconnect".
@@ -1247,7 +1237,7 @@ SEE ALSO
rclone config edit
Enter an interactive configuration session.
-Synopsis
+Synopsis
Enter an interactive configuration session where you can setup new remotes and manage existing ones. You may also set or remove a password to protect your configuration.
rclone config edit [flags]
Options
@@ -1269,7 +1259,7 @@ SEE ALSO
rclone config password
Update password in an existing remote.
-Synopsis
+Synopsis
Update an existing remote's password. The password should be passed in pairs of key password or as key=password. The password should be passed in in clear (unobscured).
For example, to set password of a remote of name myremote you would do:
rclone config password myremote fieldname mypassword
@@ -1305,7 +1295,7 @@ SEE ALSO
rclone config reconnect
Re-authenticates user with remote.
-Synopsis
+Synopsis
This reconnects remote: passed in to the cloud storage system.
To disconnect the remote use "rclone config disconnect".
This normally means going through the interactive oauth flow again.
@@ -1339,7 +1329,7 @@ SEE ALSO
rclone config update
Update options in an existing remote.
-Synopsis
+Synopsis
Update an existing remote's options. The options should be passed in pairs of key value or as key=value.
For example, to update the env_auth field of a remote of name myremote you would do:
rclone config update myremote env_auth true
@@ -1410,7 +1400,7 @@ SEE ALSO
rclone config userinfo
Prints info about logged in user of remote.
-Synopsis
+Synopsis
This prints the details of the person logged in to the cloud storage system.
rclone config userinfo remote: [flags]
Options
@@ -1423,9 +1413,9 @@ SEE ALSO
rclone copyto
Copy files from source to dest, skipping identical files.
-Synopsis
+Synopsis
If source:path is a file or directory then it copies it to a file or directory named dest:path.
-This can be used to upload single files to other than their current name. If the source is a directory then it acts exactly like the copy command.
+This can be used to upload single files to other than their current name. If the source is a directory then it acts exactly like the copy command.
So
rclone copyto src dst
where src and dst are rclone paths, either remote:path or /path/to/local or C:.
@@ -1447,18 +1437,19 @@ SEE ALSO
rclone copyurl
Copy url content to dest.
-Synopsis
+Synopsis
Download a URL's content and copy it to the destination without saving it in temporary storage.
-Setting --auto-filename will cause the file name to be retrieved from the URL (after any redirections) and used in the destination path. With --print-filename in addition, the resulting file name will be printed.
+Setting --auto-filename will attempt to automatically determine the filename from the URL (after any redirections) and used in the destination path. With --auto-filename-header in addition, if a specific filename is set in HTTP headers, it will be used instead of the name from the URL. With --print-filename in addition, the resulting file name will be printed.
Setting --no-clobber will prevent overwriting file on the destination if there is one with the same name.
Setting --stdout or making the output file name - will cause the output to be written to standard output.
rclone copyurl https://example.com dest:path [flags]
Options
- -a, --auto-filename Get the file name from the URL and use it for destination file path
- -h, --help help for copyurl
- --no-clobber Prevent overwriting file with same name
- -p, --print-filename Print the resulting name from --auto-filename
- --stdout Write the output to stdout rather than a file
+ -a, --auto-filename Get the file name from the URL and use it for destination file path
+ --header-filename Get the file name from the Content-Disposition header
+ -h, --help help for copyurl
+ --no-clobber Prevent overwriting file with same name
+ -p, --print-filename Print the resulting name from --auto-filename
+ --stdout Write the output to stdout rather than a file
See the global flags page for global options not listed here.
SEE ALSO
@@ -1466,8 +1457,8 @@ SEE ALSO
rclone cryptcheck
Cryptcheck checks the integrity of a crypted remote.
-Synopsis
-rclone cryptcheck checks a remote against a crypted remote. This is the equivalent of running rclone check, but able to check the checksums of the crypted remote.
+Synopsis
+rclone cryptcheck checks a remote against a crypted remote. This is the equivalent of running rclone check, but able to check the checksums of the crypted remote.
For it to work the underlying remote of the cryptedremote must support some kind of checksum.
It works by reading the nonce from each file on the cryptedremote: and using that to encrypt each file on the remote:. It then checks the checksum of the underlying file on the cryptedremote: against the checksum of the file it has just encrypted.
Use it like this
@@ -1502,14 +1493,14 @@ SEE ALSO
rclone cryptdecode
Cryptdecode returns unencrypted file names.
-Synopsis
+Synopsis
rclone cryptdecode returns unencrypted file names when provided with a list of encrypted file names. List limit is 10 items.
-If you supply the --reverse flag, it will return encrypted file names.
+If you supply the --reverse flag, it will return encrypted file names.
use it like this
rclone cryptdecode encryptedremote: encryptedfilename1 encryptedfilename2
rclone cryptdecode --reverse encryptedremote: filename1 filename2
-Another way to accomplish this is by using the rclone backend encode (or decode)command. See the documentation on the crypt overlay for more info.
+Another way to accomplish this is by using the rclone backend encode (or decode) command. See the documentation on the crypt overlay for more info.
rclone cryptdecode encryptedremote: encryptedfilename [flags]
Options
-h, --help help for cryptdecode
@@ -1521,7 +1512,7 @@ SEE ALSO
rclone deletefile
Remove a single file from remote.
-Synopsis
+Synopsis
Remove a single file from remote. Unlike delete it cannot be used to remove a directory and it doesn't obey include/exclude filters - if the specified file exists, it will always be removed.
rclone deletefile remote:path [flags]
Options
@@ -1533,8 +1524,8 @@ SEE ALSO
rclone genautocomplete
Output completion script for a given shell.
-Synopsis
-Generates a shell completion script for rclone. Run with --help to list the supported shells.
+Synopsis
+Generates a shell completion script for rclone. Run with --help to list the supported shells.
Options
-h, --help help for genautocomplete
See the global flags page for global options not listed here.
@@ -1547,7 +1538,7 @@ SEE ALSO
rclone genautocomplete bash
Output bash completion script for rclone.
-Synopsis
+Synopsis
Generates a bash shell autocompletion script for rclone.
This writes to /etc/bash_completion.d/rclone by default so will probably need to be run with sudo or as root, e.g.
sudo rclone genautocomplete bash
@@ -1565,7 +1556,7 @@ SEE ALSO
rclone genautocomplete fish
Output fish completion script for rclone.
-Synopsis
+Synopsis
Generates a fish autocompletion script for rclone.
This writes to /etc/fish/completions/rclone.fish by default so will probably need to be run with sudo or as root, e.g.
sudo rclone genautocomplete fish
@@ -1583,7 +1574,7 @@ SEE ALSO
rclone genautocomplete zsh
Output zsh completion script for rclone.
-Synopsis
+Synopsis
Generates a zsh autocompletion script for rclone.
This writes to /usr/share/zsh/vendor-completions/_rclone by default so will probably need to be run with sudo or as root, e.g.
sudo rclone genautocomplete zsh
@@ -1601,7 +1592,7 @@ SEE ALSO
rclone gendocs
Output markdown docs for rclone to the directory supplied.
-Synopsis
+Synopsis
This produces markdown docs for the rclone commands to the directory supplied. These are in a format suitable for hugo to render into the rclone.org website.
rclone gendocs output_directory [flags]
Options
@@ -1613,9 +1604,10 @@ SEE ALSO
rclone hashsum
Produces a hashsum file for all the objects in the path.
-Synopsis
+Synopsis
Produces a hash file for all the objects in the path using the hash named. The output is in the same format as the standard md5sum/sha1sum tool.
By default, the hash is requested from the remote. If the hash is not supported by the remote, no hash will be returned. With the download flag, the file will be downloaded from the remote and hashed locally enabling any hash for any remote.
+For the MD5 and SHA1 algorithms there are also dedicated commands, md5sum and sha1sum.
This command can also hash data received on standard input (stdin), by not passing a remote:path, or by passing a hyphen as remote:path when there is data to read (if not, the hypen will be treated literaly, as a relative path).
Run without a hash to see the list of all supported hashes, e.g.
$ rclone hashsum
@@ -1626,6 +1618,7 @@ Synopsis
* crc32
* sha256
* dropbox
+ * hidrive
* mailru
* quickxor
Then
@@ -1645,7 +1638,7 @@ SEE ALSO
rclone link
Generate public link to file/folder.
-Synopsis
+Synopsis
rclone link will create, retrieve or remove a public link to the given file or folder.
rclone link remote:path/to/file
rclone link remote:path/to/folder/
@@ -1666,9 +1659,9 @@ SEE ALSO
rclone listremotes
List all the remotes in the config file.
-Synopsis
+Synopsis
rclone listremotes lists all the available remotes from the config file.
-When uses with the -l flag it lists the types too.
+When used with the --long flag it lists the types too.
rclone listremotes [flags]
Options
-h, --help help for listremotes
@@ -1680,7 +1673,7 @@ SEE ALSO
rclone lsf
List directories and objects in remote:path formatted for parsing.
-Synopsis
+Synopsis
List the contents of the source path (directories and objects) to standard output in a form which is easy to parse by scripts. By default this will just be the names of the objects and directories, one per line. The directories will have a / suffix.
Eg
$ rclone lsf swift:bucket
@@ -1689,7 +1682,7 @@ Synopsis
diwogej7
ferejej3gux/
fubuwic
-Use the --format option to control what gets listed. By default this is just the path, but you can use these parameters to control the output:
+Use the --format option to control what gets listed. By default this is just the path, but you can use these parameters to control the output:
p - path
s - size
t - modification time
@@ -1698,8 +1691,9 @@ Synopsis
o - Original ID of underlying object
m - MimeType of object if known
e - encrypted name
-T - tier of storage if known, e.g. "Hot" or "Cool"
-So if you wanted the path, size and modification time, you would use --format "pst", or maybe --format "tsp" to put the path last.
+T - tier of storage if known, e.g. "Hot" or "Cool"
+M - Metadata of object in JSON blob format, eg {"key":"value"}
+So if you wanted the path, size and modification time, you would use --format "pst", or maybe --format "tsp" to put the path last.
Eg
$ rclone lsf --format "tsp" swift:bucket
2016-06-25 18:55:41;60295;bevajer5jef
@@ -1707,7 +1701,7 @@ Synopsis
2016-06-25 18:55:43;94467;diwogej7
2018-04-26 08:50:45;0;ferejej3gux/
2016-06-25 18:55:40;37600;fubuwic
-If you specify "h" in the format you will get the MD5 hash by default, use the "--hash" flag to change which hash you want. Note that this can be returned as an empty string if it isn't available on the object (and for directories), "ERROR" if there was an error reading it from the object and "UNSUPPORTED" if that object does not support that hash type.
+If you specify "h" in the format you will get the MD5 hash by default, use the --hash flag to change which hash you want. Note that this can be returned as an empty string if it isn't available on the object (and for directories), "ERROR" if there was an error reading it from the object and "UNSUPPORTED" if that object does not support that hash type.
For example, to emulate the md5sum command you can use
rclone lsf -R --hash MD5 --format hp --separator " " --files-only .
Eg
@@ -1718,7 +1712,7 @@ Synopsis
8fd37c3810dd660778137ac3a66cc06d fubuwic
99713e14a4c4ff553acaf1930fad985b gixacuh7ku
(Though "rclone md5sum ." is an easier way of typing this.)
-By default the separator is ";" this can be changed with the --separator flag. Note that separators aren't escaped in the path so putting it last is a good strategy.
+By default the separator is ";" this can be changed with the --separator flag. Note that separators aren't escaped in the path so putting it last is a good strategy.
Eg
$ rclone lsf --separator "," --format "tshp" swift:bucket
2016-06-25 18:55:41,60295,7908e352297f0f530b84a756f188baa3,bevajer5jef
@@ -1732,7 +1726,7 @@ Synopsis
test.log,22355
test.sh,449
"this file contains a comma, in the file name.txt",6
-Note that the --absolute parameter is useful for making lists of files to pass to an rclone copy with the --files-from-raw flag.
+Note that the --absolute parameter is useful for making lists of files to pass to an rclone copy with the --files-from-raw flag.
For example, to find all the files modified within one day and copy those only (without traversing the whole directory structure):
rclone lsf --absolute --files-only --max-age 1d /path/to/local > new_files
rclone copy --files-from-raw new_files /path/to/local remote:path
@@ -1768,18 +1762,37 @@ SEE ALSO
rclone lsjson
List directories and objects in the path in JSON format.
-Synopsis
+Synopsis
List directories and objects in the path in JSON format.
The output is an array of Items, where each Item looks like this
-{ "Hashes" : { "SHA-1" : "f572d396fae9206628714fb2ce00f72e94f2258f", "MD5" : "b1946ac92492d2347c6235b4d2611184", "DropboxHash" : "ecb65bb98f9d905b70458986c39fcbad7715e5f2fcc3b1f07767d7c83e2438cc" }, "ID": "y2djkhiujf83u33", "OrigID": "UYOJVTUW00Q1RzTDA", "IsBucket" : false, "IsDir" : false, "MimeType" : "application/octet-stream", "ModTime" : "2017-05-31T16:15:57.034468261+01:00", "Name" : "file.txt", "Encrypted" : "v0qpsdq8anpci8n929v3uu9338", "EncryptedPath" : "kja9098349023498/v0qpsdq8anpci8n929v3uu9338", "Path" : "full/path/goes/here/file.txt", "Size" : 6, "Tier" : "hot", }
-If --hash is not specified the Hashes property won't be emitted. The types of hash can be specified with the --hash-type parameter (which may be repeated). If --hash-type is set then it implies --hash.
-If --no-modtime is specified then ModTime will be blank. This can speed things up on remotes where reading the ModTime takes an extra request (e.g. s3, swift).
-If --no-mimetype is specified then MimeType will be blank. This can speed things up on remotes where reading the MimeType takes an extra request (e.g. s3, swift).
-If --encrypted is not specified the Encrypted won't be emitted.
-If --dirs-only is not specified files in addition to directories are returned
-If --files-only is not specified directories in addition to the files will be returned.
-if --stat is set then a single JSON blob will be returned about the item pointed to. This will return an error if the item isn't found. However on bucket based backends (like s3, gcs, b2, azureblob etc) if the item isn't found it will return an empty directory as it isn't possible to tell empty directories from missing directories there.
-The Path field will only show folders below the remote path being listed. If "remote:path" contains the file "subfolder/file.txt", the Path for "file.txt" will be "subfolder/file.txt", not "remote:path/subfolder/file.txt". When used without --recursive the Path will always be the same as Name.
+{
+ "Hashes" : {
+ "SHA-1" : "f572d396fae9206628714fb2ce00f72e94f2258f",
+ "MD5" : "b1946ac92492d2347c6235b4d2611184",
+ "DropboxHash" : "ecb65bb98f9d905b70458986c39fcbad7715e5f2fcc3b1f07767d7c83e2438cc"
+ },
+ "ID": "y2djkhiujf83u33",
+ "OrigID": "UYOJVTUW00Q1RzTDA",
+ "IsBucket" : false,
+ "IsDir" : false,
+ "MimeType" : "application/octet-stream",
+ "ModTime" : "2017-05-31T16:15:57.034468261+01:00",
+ "Name" : "file.txt",
+ "Encrypted" : "v0qpsdq8anpci8n929v3uu9338",
+ "EncryptedPath" : "kja9098349023498/v0qpsdq8anpci8n929v3uu9338",
+ "Path" : "full/path/goes/here/file.txt",
+ "Size" : 6,
+ "Tier" : "hot",
+}
+If --hash is not specified the Hashes property won't be emitted. The types of hash can be specified with the --hash-type parameter (which may be repeated). If --hash-type is set then it implies --hash.
+If --no-modtime is specified then ModTime will be blank. This can speed things up on remotes where reading the ModTime takes an extra request (e.g. s3, swift).
+If --no-mimetype is specified then MimeType will be blank. This can speed things up on remotes where reading the MimeType takes an extra request (e.g. s3, swift).
+If --encrypted is not specified the Encrypted won't be emitted.
+If --dirs-only is not specified files in addition to directories are returned
+If --files-only is not specified directories in addition to the files will be returned.
+If --metadata is set then an additional Metadata key will be returned. This will have metdata in rclone standard format as a JSON object.
+if --stat is set then a single JSON blob will be returned about the item pointed to. This will return an error if the item isn't found. However on bucket based backends (like s3, gcs, b2, azureblob etc) if the item isn't found it will return an empty directory as it isn't possible to tell empty directories from missing directories there.
+The Path field will only show folders below the remote path being listed. If "remote:path" contains the file "subfolder/file.txt", the Path for "file.txt" will be "subfolder/file.txt", not "remote:path/subfolder/file.txt". When used without --recursive the Path will always be the same as Name.
If the directory is a bucket in a bucket-based backend, then "IsBucket" will be set to true. This key won't be present unless it is "true".
The time is in RFC3339 format with up to nanosecond precision. The number of decimal digits in the seconds will depend on the precision that the remote can hold the times, so if times are accurate to the nearest millisecond (e.g. Google Drive) then 3 digits will always be shown ("2017-05-31T16:15:57.034+01:00") whereas if the times are accurate to the nearest second (Dropbox, Box, WebDav, etc.) no digits will be shown ("2017-05-31T16:15:57+01:00").
The whole output can be processed as a JSON blob, or alternatively it can be processed line by line as each item is written one to a line.
@@ -1799,7 +1812,7 @@ Synopsis
rclone lsjson remote:path [flags]
Options
--dirs-only Show only directories in the listing
- -M, --encrypted Show the encrypted names
+ --encrypted Show the encrypted names
--files-only Show only files in the listing
--hash Include hashes in the output (may take longer)
--hash-type stringArray Show only this hash type (may be repeated)
@@ -1816,7 +1829,7 @@ SEE ALSO
rclone mount
Mount the remote as file system on a mountpoint.
-Synopsis
+Synopsis
rclone mount allows Linux, FreeBSD, macOS and Windows to mount any of Rclone's cloud storage systems as a file system with FUSE.
First set up your remote using rclone config. Check it works with rclone ls etc.
On Linux and macOS, you can run mount in either foreground or background (aka daemon) mode. Mount runs in foreground mode by default. Use the --daemon flag to force background mode. On Windows you can run mount in foreground only, the flag is ignored.
@@ -1839,7 +1852,7 @@ Synopsis
The size of the mounted file system will be set according to information retrieved from the remote, the same as returned by the rclone about command. Remotes with unlimited storage may report the used size only, then an additional 1 PiB of free space is assumed. If the remote does not support the about feature at all, then 1 PiB is set as both the total and the free size.
Installing on Windows
To run rclone mount on Windows, you will need to download and install WinFsp.
-WinFsp is an open-source Windows File System Proxy which makes it easy to write user space file systems for Windows. It provides a FUSE emulation layer which rclone uses combination with cgofuse. Both of these packages are by Bill Zissimopoulos who was very helpful during the implementation of rclone mount for Windows.
+WinFsp is an open-source Windows File System Proxy which makes it easy to write user space file systems for Windows. It provides a FUSE emulation layer which rclone uses combination with cgofuse. Both of these packages are by Bill Zissimopoulos who was very helpful during the implementation of rclone mount for Windows.
Mounting modes on windows
Unlike other operating systems, Microsoft Windows provides a different filesystem type for network and fixed drives. It optimises access on the assumption fixed disk drives are fast and reliable, while network drives have relatively high latency and less reliability. Some settings can also be differentiated between the two types, for example that Windows Explorer should just display icons and not create preview thumbnails for image and video files on network drives.
In most cases, rclone will mount the remote as a normal, fixed disk drive by default. However, you can also choose to mount it as a remote network drive, often described as a network share. If you mount an rclone remote using the default, fixed drive mode and experience unexpected program errors, freezes or other issues, consider mounting as a network drive instead.
@@ -1873,7 +1886,7 @@ Windows caveats
Drives created as Administrator are not visible to other accounts, not even an account that was elevated to Administrator with the User Account Control (UAC) feature. A result of this is that if you mount to a drive letter from a Command Prompt run as Administrator, and then try to access the same drive from Windows Explorer (which does not run as Administrator), you will not be able to see the mounted drive.
If you don't need to access the drive from applications running with administrative privileges, the easiest way around this is to always create the mount from a non-elevated command prompt.
To make mapped drives available to the user account that created them regardless if elevated or not, there is a special Windows setting called linked connections that can be enabled.
-It is also possible to make a drive mount available to everyone on the system, by running the process creating it as the built-in SYSTEM account. There are several ways to do this: One is to use the command-line utility PsExec, from Microsoft's Sysinternals suite, which has option -s to start processes as the SYSTEM account. Another alternative is to run the mount command from a Windows Scheduled Task, or a Windows Service, configured to run as the SYSTEM account. A third alternative is to use the WinFsp.Launcher infrastructure). Note that when running rclone as another user, it will not use the configuration file from your profile unless you tell it to with the --config option. Read more in the install documentation.
+It is also possible to make a drive mount available to everyone on the system, by running the process creating it as the built-in SYSTEM account. There are several ways to do this: One is to use the command-line utility PsExec, from Microsoft's Sysinternals suite, which has option -s to start processes as the SYSTEM account. Another alternative is to run the mount command from a Windows Scheduled Task, or a Windows Service, configured to run as the SYSTEM account. A third alternative is to use the WinFsp.Launcher infrastructure). Note that when running rclone as another user, it will not use the configuration file from your profile unless you tell it to with the --config option. Read more in the install documentation.
Note that mapping to a directory path, instead of a drive letter, does not suffer from the same limitations.
Limitations
Without the use of --vfs-cache-mode this can only write files sequentially, it can only seek when reading. This means that many applications won't work with their files on an rclone mount without --vfs-cache-mode writes or --vfs-cache-mode full. See the VFS File Caching section for more info.
@@ -1936,7 +1949,7 @@ VFS - Virtual File System
Cloud storage objects have lots of properties which aren't like disk files - you can't extend them or write to the middle of them, so the VFS layer has to deal with that. Because there is no one right way of doing this there are various options explained below.
The VFS layer also implements a directory cache - this caches info about files and directories (but not the data) in memory.
VFS Directory Cache
-Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the mount will appear immediately or invalidate the cache.
+Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the VFS will appear immediately or invalidate the cache.
--dir-cache-time duration Time to cache directory entries for (default 5m0s)
--poll-interval duration Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable (default 1m0s)
However, changes made directly on the cloud storage by the web interface or a different copy of rclone will only be picked up once the directory cache expires if the backend configured does not support polling for changes. If the backend supports polling, changes will be picked up within the polling interval.
@@ -1999,6 +2012,19 @@ --vfs-cache-mode full
When reading a file rclone will read --buffer-size plus --vfs-read-ahead bytes ahead. The --buffer-size is buffered in memory whereas the --vfs-read-ahead is buffered on disk.
When using this mode it is recommended that --buffer-size is not set too large and --vfs-read-ahead is set large if required.
IMPORTANT not all file systems support sparse files. In particular FAT/exFAT do not. Rclone will perform very badly if the cache directory is on a filesystem which doesn't support sparse files and it will log an ERROR message if one is detected.
+Fingerprinting
+Various parts of the VFS use fingerprinting to see if a local file copy has changed relative to a remote file. Fingerprints are made from:
+
+- size
+- modification time
+- hash
+
+where available on an object.
+On some backends some of these attributes are slow to read (they take an extra API call per object, or extra work per object).
+For example hash is slow with the local and sftp backends as they have to read the entire file and hash it, and modtime is slow with the s3, swift, ftp and qinqstor backends because they need to do an extra API call to fetch it.
+If you use the --vfs-fast-fingerprint flag then rclone will not include the slow operations in the fingerprint. This makes the fingerprinting less accurate but much faster and will improve the opening time of cached files.
+If you are running a vfs cache over local, s3 or swift backends then using this flag is recommended.
+Note that if you change the value of this flag, the fingerprints of the files in the cache may be invalidated and the files will need to be downloaded again.
VFS Chunked Reading
When rclone reads files from a remote it reads them in chunks. This means that rather than requesting the whole file rclone reads the chunk specified. This can reduce the used download quota for some remotes by requesting only chunks from the remote that are actually read, at the cost of an increased number of requests.
These flags control the chunking:
@@ -2013,20 +2039,23 @@
--no-checksum Don't compare checksums on up/download.
--no-modtime Don't read/write the modification time (can speed things up).
--no-seek Don't allow seeking in files.
---read-only Mount read-only.
+--read-only Only allow read-only access.
Sometimes rclone is delivered reads or writes out of order. Rather than seeking rclone will wait a short time for the in sequence read or write to come in. These flags only come into effect when not using an on disk cache file.
--vfs-read-wait duration Time to wait for in-sequence read before seeking (default 20ms)
--vfs-write-wait duration Time to wait for in-sequence write before giving error (default 1s)
-When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from cache (the related global flag --checkers have no effect on mount).
+When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from the cache (the related global flag --checkers has no effect on the VFS).
--transfers int Number of file transfers to run in parallel (default 4)
VFS Case Sensitivity
Linux file systems are case-sensitive: two files can differ only by case, and the exact case must be used when opening a file.
File systems in modern Windows are case-insensitive but case-preserving: although existing files can be opened using any case, the exact case used to create the file is preserved and available for programs to query. It is not allowed for two files in the same directory to differ only by case.
Usually file systems on macOS are case-insensitive. It is possible to make macOS file systems case-sensitive but that is not the default.
-The --vfs-case-insensitive mount flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the mounted file system as-is. If the flag is "true" (or appears without a value on command line), rclone may perform a "fixup" as explained below.
-The user may specify a file name to open/delete/rename/etc with a case different than what is stored on mounted file system. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by an underlying mounted file system.
-Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system mounted by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
+The --vfs-case-insensitive VFS flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the remote as-is. If the flag is "true" (or appears without a value on the command line), rclone may perform a "fixup" as explained below.
+The user may specify a file name to open/delete/rename/etc with a case different than what is stored on the remote. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by the underlying remote.
+Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system presented by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
If the flag is not provided on the command line, then its default value depends on the operating system where rclone runs: "true" on Windows and macOS, "false" otherwise. If the flag is provided without a value, then it is "true".
+VFS Disk Options
+This flag allows you to manually set the statistics about the filing system. It can be useful when those statistics cannot be read correctly automatically.
+--vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used. If you need this information to be available when running df on the filesystem, then pass the flag --vfs-used-is-size to rclone. With this flag set, instead of relying on the backend to report this information, rclone will scan the whole remote similar to rclone size and compute the total used space itself.
WARNING. Contrary to rclone size, this flag ignores filters so that the result is accurate. However, this is very inefficient and may cost lots of API calls resulting in extra charges. Use it as a last resort and only with caching.
@@ -2058,7 +2087,7 @@ Options
--noapplexattr Ignore all "com.apple.*" extended attributes (supported on OSX only)
-o, --option stringArray Option for libfuse/WinFsp (repeat if required)
--poll-interval duration Time to wait between polling for changes, must be smaller than dir-cache-time and only on supported remotes (set 0 to disable) (default 1m0s)
- --read-only Mount read-only
+ --read-only Only allow read-only access
--uid uint32 Override the uid field set by the filesystem (not supported on Windows) (default 1000)
--umask int Override the permission bits set by the filesystem (not supported on Windows) (default 2)
--vfs-cache-max-age duration Max age of objects in the cache (default 1h0m0s)
@@ -2066,6 +2095,8 @@ Options
--vfs-cache-mode CacheMode Cache mode off|minimal|writes|full (default off)
--vfs-cache-poll-interval duration Interval to poll the cache for stale objects (default 1m0s)
--vfs-case-insensitive If a file name not found, find a case insensitive match
+ --vfs-disk-space-total-size SizeSuffix Specify the total space of disk (default off)
+ --vfs-fast-fingerprint Use fast (less accurate) fingerprints for change detection
--vfs-read-ahead SizeSuffix Extra read ahead over --buffer-size when using cache-mode full
--vfs-read-chunk-size SizeSuffix Read the source objects in chunks (default 128Mi)
--vfs-read-chunk-size-limit SizeSuffix If greater than --vfs-read-chunk-size, double the chunk size after each chunk read, until the limit is reached ('off' is unlimited) (default off)
@@ -2082,9 +2113,9 @@ SEE ALSO
rclone moveto
Move file or directory from source to dest.
-Synopsis
+Synopsis
If source:path is a file or directory then it moves it to a file or directory named dest:path.
-This can be used to rename files or upload single files to other than their existing name. If the source is a directory then it acts exactly like the move command.
+This can be used to rename files or upload single files to other than their existing name. If the source is a directory then it acts exactly like the move command.
So
rclone moveto src dst
where src and dst are rclone paths, either remote:path or /path/to/local or C:.
@@ -2107,10 +2138,10 @@ SEE ALSO
rclone ncdu
Explore a remote with a text based user interface.
-Synopsis
+Synopsis
This displays a text based user interface allowing the navigation of a remote. It is most useful for answering the question - "What is using all my disk space?".
To make the user interface it first scans the entire remote given and builds an in memory representation. rclone ncdu can be used during this scanning phase and you will see it building up the directory structure as it goes along.
-Here are the keys - press '?' to toggle the help on and off
+You can interact with the user interface using key presses, press '?' to toggle the help on and off. The supported keys are:
↑,↓ or k,j to Move
→,l to enter
←,h to return
@@ -2120,13 +2151,28 @@ Synopsis
u toggle human-readable format
n,s,C,A sort by name,size,count,average size
d delete file/directory
+ v select file/directory
+ V enter visual select mode
+ D delete selected files/directories
y copy current path to clipboard
Y display current path
- ^L refresh screen
+ ^L refresh screen (fix screen corruption)
? to toggle help on and off
- q/ESC/c-C to quit
+ q/ESC/^c to quit
+Listed files/directories may be prefixed by a one-character flag, some of them combined with a description in brackes at end of line. These flags have the following meaning:
+e means this is an empty directory, i.e. contains no files (but
+ may contain empty subdirectories)
+~ means this is a directory where some of the files (possibly in
+ subdirectories) have unknown size, and therefore the directory
+ size may be underestimated (and average size inaccurate, as it
+ is average of the files with known sizes).
+. means an error occurred while reading a subdirectory, and
+ therefore the directory size may be underestimated (and average
+ size inaccurate)
+! means an error occurred while reading this directory
This an homage to the ncdu tool but for rclone remotes. It is missing lots of features at the moment but is useful as it stands.
-Note that it might take some time to delete big files/folders. The UI won't respond in the meantime since the deletion is done synchronously.
+Note that it might take some time to delete big files/directories. The UI won't respond in the meantime since the deletion is done synchronously.
+For a non-interactive listing of the remote, see the tree command. To just get the total size of the remote you can also use the size command.
rclone ncdu remote:path [flags]
Options
-h, --help help for ncdu
@@ -2137,11 +2183,11 @@ SEE ALSO
rclone obscure
Obscure password for use in the rclone config file.
-Synopsis
+Synopsis
In the rclone config file, human-readable passwords are obscured. Obscuring them is done by encrypting them and writing them out in base64. This is not a secure way of encrypting these passwords as rclone can decrypt them - it is to prevent "eyedropping" - namely someone seeing a password in the rclone config file by accident.
Many equally important things (like access tokens) are not obscured in the config file. However it is very hard to shoulder surf a 64 character hex token.
This command can also accept a password through STDIN instead of an argument by passing a hyphen as an argument. This will use the first line of STDIN as the password not including the trailing newline.
-echo "secretpassword" | rclone obscure -
+echo "secretpassword" | rclone obscure -
If there is no data on STDIN to read, rclone obscure will default to obfuscating the hyphen itself.
If you want to encrypt the config file then please use config file encryption - see rclone config for more info.
rclone obscure password [flags]
@@ -2154,24 +2200,24 @@ SEE ALSO
rclone rc
Run a command against a running rclone.
-Synopsis
-This runs a command against a running rclone. Use the --url flag to specify an non default URL to connect on. This can be either a ":port" which is taken to mean "http://localhost:port" or a "host:port" which is taken to mean "http://host:port"
-A username and password can be passed in with --user and --pass.
-Note that --rc-addr, --rc-user, --rc-pass will be read also for --url, --user, --pass.
+Synopsis
+This runs a command against a running rclone. Use the --url flag to specify an non default URL to connect on. This can be either a ":port" which is taken to mean "http://localhost:port" or a "host:port" which is taken to mean "http://host:port"
+A username and password can be passed in with --user and --pass.
+Note that --rc-addr, --rc-user, --rc-pass will be read also for --url, --user, --pass.
Arguments should be passed in as parameter=value.
The result will be returned as a JSON object by default.
-The --json parameter can be used to pass in a JSON blob as an input instead of key=value arguments. This is the only way of passing in more complicated values.
-The -o/--opt option can be used to set a key "opt" with key, value options in the form "-o key=value" or "-o key". It can be repeated as many times as required. This is useful for rc commands which take the "opt" parameter which by convention is a dictionary of strings.
+The --json parameter can be used to pass in a JSON blob as an input instead of key=value arguments. This is the only way of passing in more complicated values.
+The -o/--opt option can be used to set a key "opt" with key, value options in the form -o key=value or -o key. It can be repeated as many times as required. This is useful for rc commands which take the "opt" parameter which by convention is a dictionary of strings.
-o key=value -o key2
Will place this in the "opt" value
{"key":"value", "key2","")
-The -a/--arg option can be used to set strings in the "arg" value. It can be repeated as many times as required. This is useful for rc commands which take the "arg" parameter which by convention is a list of strings.
+The -a/--arg option can be used to set strings in the "arg" value. It can be repeated as many times as required. This is useful for rc commands which take the "arg" parameter which by convention is a list of strings.
-a value -a value2
Will place this in the "arg" value
["value", "value2"]
-Use --loopback to connect to the rclone instance running "rclone rc". This is very useful for testing commands without having to run an rclone rc server, e.g.:
+Use --loopback to connect to the rclone instance running rclone rc. This is very useful for testing commands without having to run an rclone rc server, e.g.:
rclone rc --loopback operations/about fs=/
-Use "rclone rc" to see a list of all possible commands.
+Use rclone rc to see a list of all possible commands.
rclone rc commands parameter [flags]
Options
-a, --arg stringArray Argument placed in the "arg" array
@@ -2190,14 +2236,14 @@ SEE ALSO
rclone rcat
Copies standard input to file on remote.
-Synopsis
+Synopsis
rclone rcat reads from standard input (stdin) and copies it to a single remote file.
echo "hello world" | rclone rcat remote:path/to/file
ffmpeg - | rclone rcat remote:path/to/file
If the remote file already exists, it will be overwritten.
rcat will try to upload small files in a single request, which is usually more efficient than the streaming/chunked upload endpoints, which use multiple requests. Exact behaviour depends on the remote. What is considered a small file may be set through --streaming-upload-cutoff. Uploading only starts after the cutoff is reached or if the file ends before that. The data must fit into RAM. The cutoff needs to be small enough to adhere the limits of your remote, please see there. Generally speaking, setting this cutoff too high will decrease your performance.
-Use the |--size| flag to preallocate the file in advance at the remote end and actually stream it, even if remote backend doesn't support streaming.
-|--size| should be the exact size of the input stream in bytes. If the size of the stream is different in length to the |--size| passed in then the transfer will likely fail.
+Use the --size flag to preallocate the file in advance at the remote end and actually stream it, even if remote backend doesn't support streaming.
+--size should be the exact size of the input stream in bytes. If the size of the stream is different in length to the --size passed in then the transfer will likely fail.
Note that the upload can also not be retried because the data is not kept around until the upload succeeds. If you need to transfer a lot of data, you're better off caching locally and then rclone move it to the destination.
rclone rcat remote:path [flags]
Options
@@ -2210,7 +2256,7 @@ SEE ALSO
rclone rcd
Run rclone listening to remote control commands only.
-Synopsis
+Synopsis
This runs rclone so that it only listens to remote control commands.
This is useful if you are controlling rclone via the rc API.
If you pass in a path to a directory, rclone will serve that directory for GET requests on the URL passed in. It will also open the URL in the browser when rclone is run.
@@ -2225,11 +2271,11 @@ SEE ALSO
rclone rmdirs
Remove empty directories under the path.
-Synopsis
+Synopsis
This recursively removes any empty directories (including directories that only contain empty directories), that it finds under the path. The root path itself will also be removed if it is empty, unless you supply the --leave-root flag.
-Use command rmdir to delete just the empty directory given by path, not recurse.
-This is useful for tidying up remotes that rclone has left a lot of empty directories in. For example the delete command will delete files but leave the directory structure (unless used with option --rmdirs).
-To delete a path and any objects in it, use purge command.
+Use command rmdir to delete just the empty directory given by path, not recurse.
+This is useful for tidying up remotes that rclone has left a lot of empty directories in. For example the delete command will delete files but leave the directory structure (unless used with option --rmdirs).
+To delete a path and any objects in it, use purge command.
rclone rmdirs remote:path [flags]
Options
-h, --help help for rmdirs
@@ -2241,7 +2287,7 @@ SEE ALSO
rclone selfupdate
Update the rclone binary.
-Synopsis
+Synopsis
This command downloads the latest release of rclone and replaces the currently running binary. The download is verified with a hashsum and cryptographically signed signature.
If used without flags (or with implied --stable flag), this command will install the latest stable release. However, some issues may be fixed (or features added) only in the latest beta release. In such cases you should run the command with the --beta flag, i.e. rclone selfupdate --beta. You can check in advance what version would be installed by adding the --check flag, then repeat the command without it when you are satisfied.
Sometimes the rclone team may recommend you a concrete beta or stable rclone release to troubleshoot your issue or add a bleeding edge feature. The --version VER flag, if given, will update to the concrete version instead of the latest one. If you omit micro version from VER (for example 1.53), the latest matching micro version will be used.
@@ -2266,8 +2312,8 @@ SEE ALSO
rclone serve
Serve a remote over a protocol.
-Synopsis
-rclone serve is used to serve a remote over a given protocol. This command requires the use of a subcommand to specify the protocol, e.g.
+Synopsis
+Serve a remote over a given protocol. Requires the use of a subcommand to specify the protocol, e.g.
rclone serve http remote:
Each subcommand has its own options which you can see in their help.
rclone serve <protocol> [opts] <remote> [flags]
@@ -2283,12 +2329,12 @@ SEE ALSO
rclone serve http - Serve the remote over HTTP.
rclone serve restic - Serve the remote for restic's REST API.
rclone serve sftp - Serve the remote over SFTP.
-rclone serve webdav - Serve remote:path over webdav.
+rclone serve webdav - Serve remote:path over WebDAV.
rclone serve dlna
Serve remote:path over DLNA
-Synopsis
-rclone serve dlna is a DLNA media server for media stored in an rclone remote. Many devices, such as the Xbox and PlayStation, can automatically discover this server in the LAN and play audio/video from it. VLC is also supported. Service discovery uses UDP multicast packets (SSDP) and will thus only work on LANs.
+Synopsis
+Run a DLNA media server for media stored in an rclone remote. Many devices, such as the Xbox and PlayStation, can automatically discover this server in the LAN and play audio/video from it. VLC is also supported. Service discovery uses UDP multicast packets (SSDP) and will thus only work on LANs.
Rclone will list all files present in the remote, without filtering based on media formats or file extensions. Additionally, there is no media transcoding support. This means that some players might show files that they are not able to play back correctly.
Server options
Use --addr to specify which IP address and port the server should listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs.
@@ -2299,7 +2345,7 @@ VFS - Virtual File System
Cloud storage objects have lots of properties which aren't like disk files - you can't extend them or write to the middle of them, so the VFS layer has to deal with that. Because there is no one right way of doing this there are various options explained below.
The VFS layer also implements a directory cache - this caches info about files and directories (but not the data) in memory.
VFS Directory Cache
-Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the mount will appear immediately or invalidate the cache.
+Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the VFS will appear immediately or invalidate the cache.
--dir-cache-time duration Time to cache directory entries for (default 5m0s)
--poll-interval duration Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable (default 1m0s)
However, changes made directly on the cloud storage by the web interface or a different copy of rclone will only be picked up once the directory cache expires if the backend configured does not support polling for changes. If the backend supports polling, changes will be picked up within the polling interval.
@@ -2362,6 +2408,19 @@ --vfs-cache-mode full
When reading a file rclone will read --buffer-size plus --vfs-read-ahead bytes ahead. The --buffer-size is buffered in memory whereas the --vfs-read-ahead is buffered on disk.
When using this mode it is recommended that --buffer-size is not set too large and --vfs-read-ahead is set large if required.
IMPORTANT not all file systems support sparse files. In particular FAT/exFAT do not. Rclone will perform very badly if the cache directory is on a filesystem which doesn't support sparse files and it will log an ERROR message if one is detected.
+Fingerprinting
+Various parts of the VFS use fingerprinting to see if a local file copy has changed relative to a remote file. Fingerprints are made from:
+
+- size
+- modification time
+- hash
+
+where available on an object.
+On some backends some of these attributes are slow to read (they take an extra API call per object, or extra work per object).
+For example hash is slow with the local and sftp backends as they have to read the entire file and hash it, and modtime is slow with the s3, swift, ftp and qinqstor backends because they need to do an extra API call to fetch it.
+If you use the --vfs-fast-fingerprint flag then rclone will not include the slow operations in the fingerprint. This makes the fingerprinting less accurate but much faster and will improve the opening time of cached files.
+If you are running a vfs cache over local, s3 or swift backends then using this flag is recommended.
+Note that if you change the value of this flag, the fingerprints of the files in the cache may be invalidated and the files will need to be downloaded again.
VFS Chunked Reading
When rclone reads files from a remote it reads them in chunks. This means that rather than requesting the whole file rclone reads the chunk specified. This can reduce the used download quota for some remotes by requesting only chunks from the remote that are actually read, at the cost of an increased number of requests.
These flags control the chunking:
@@ -2376,20 +2435,23 @@
--no-checksum Don't compare checksums on up/download.
--no-modtime Don't read/write the modification time (can speed things up).
--no-seek Don't allow seeking in files.
---read-only Mount read-only.
+--read-only Only allow read-only access.
Sometimes rclone is delivered reads or writes out of order. Rather than seeking rclone will wait a short time for the in sequence read or write to come in. These flags only come into effect when not using an on disk cache file.
--vfs-read-wait duration Time to wait for in-sequence read before seeking (default 20ms)
--vfs-write-wait duration Time to wait for in-sequence write before giving error (default 1s)
-When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from cache (the related global flag --checkers have no effect on mount).
+When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from the cache (the related global flag --checkers has no effect on the VFS).
--transfers int Number of file transfers to run in parallel (default 4)
VFS Case Sensitivity
Linux file systems are case-sensitive: two files can differ only by case, and the exact case must be used when opening a file.
File systems in modern Windows are case-insensitive but case-preserving: although existing files can be opened using any case, the exact case used to create the file is preserved and available for programs to query. It is not allowed for two files in the same directory to differ only by case.
Usually file systems on macOS are case-insensitive. It is possible to make macOS file systems case-sensitive but that is not the default.
-The --vfs-case-insensitive mount flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the mounted file system as-is. If the flag is "true" (or appears without a value on command line), rclone may perform a "fixup" as explained below.
-The user may specify a file name to open/delete/rename/etc with a case different than what is stored on mounted file system. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by an underlying mounted file system.
-Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system mounted by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
+The --vfs-case-insensitive VFS flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the remote as-is. If the flag is "true" (or appears without a value on the command line), rclone may perform a "fixup" as explained below.
+The user may specify a file name to open/delete/rename/etc with a case different than what is stored on the remote. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by the underlying remote.
+Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system presented by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
If the flag is not provided on the command line, then its default value depends on the operating system where rclone runs: "true" on Windows and macOS, "false" otherwise. If the flag is provided without a value, then it is "true".
+VFS Disk Options
+This flag allows you to manually set the statistics about the filing system. It can be useful when those statistics cannot be read correctly automatically.
+--vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used. If you need this information to be available when running df on the filesystem, then pass the flag --vfs-used-is-size to rclone. With this flag set, instead of relying on the backend to report this information, rclone will scan the whole remote similar to rclone size and compute the total used space itself.
WARNING. Contrary to rclone size, this flag ignores filters so that the result is accurate. However, this is very inefficient and may cost lots of API calls resulting in extra charges. Use it as a last resort and only with caching.
@@ -2407,7 +2469,7 @@ Options
--no-modtime Don't read/write the modification time (can speed things up)
--no-seek Don't allow seeking in files
--poll-interval duration Time to wait between polling for changes, must be smaller than dir-cache-time and only on supported remotes (set 0 to disable) (default 1m0s)
- --read-only Mount read-only
+ --read-only Only allow read-only access
--uid uint32 Override the uid field set by the filesystem (not supported on Windows) (default 1000)
--umask int Override the permission bits set by the filesystem (not supported on Windows) (default 2)
--vfs-cache-max-age duration Max age of objects in the cache (default 1h0m0s)
@@ -2415,6 +2477,8 @@ Options
--vfs-cache-mode CacheMode Cache mode off|minimal|writes|full (default off)
--vfs-cache-poll-interval duration Interval to poll the cache for stale objects (default 1m0s)
--vfs-case-insensitive If a file name not found, find a case insensitive match
+ --vfs-disk-space-total-size SizeSuffix Specify the total space of disk (default off)
+ --vfs-fast-fingerprint Use fast (less accurate) fingerprints for change detection
--vfs-read-ahead SizeSuffix Extra read ahead over --buffer-size when using cache-mode full
--vfs-read-chunk-size SizeSuffix Read the source objects in chunks (default 128Mi)
--vfs-read-chunk-size-limit SizeSuffix If greater than --vfs-read-chunk-size, double the chunk size after each chunk read, until the limit is reached ('off' is unlimited) (default off)
@@ -2429,7 +2493,7 @@ SEE ALSO
rclone serve docker
Serve any remote on docker's volume plugin API.
-Synopsis
+Synopsis
This command implements the Docker volume plugin API allowing docker to use rclone as a data storage mechanism for various cloud providers. rclone provides docker volume plugin based on it.
To create a docker plugin, one must create a Unix or TCP socket that Docker will look for when you use the plugin and then it listens for commands from docker daemon and runs the corresponding code when necessary. Docker plugins can run as a managed plugin under control of the docker daemon or as an independent native service. For testing, you can just run it directly from the command line, for example:
sudo rclone serve docker --base-dir /tmp/rclone-volumes --socket-addr localhost:8787 -vv
@@ -2442,7 +2506,7 @@ VFS - Virtual File System
Cloud storage objects have lots of properties which aren't like disk files - you can't extend them or write to the middle of them, so the VFS layer has to deal with that. Because there is no one right way of doing this there are various options explained below.
The VFS layer also implements a directory cache - this caches info about files and directories (but not the data) in memory.
VFS Directory Cache
-Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the mount will appear immediately or invalidate the cache.
+Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the VFS will appear immediately or invalidate the cache.
--dir-cache-time duration Time to cache directory entries for (default 5m0s)
--poll-interval duration Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable (default 1m0s)
However, changes made directly on the cloud storage by the web interface or a different copy of rclone will only be picked up once the directory cache expires if the backend configured does not support polling for changes. If the backend supports polling, changes will be picked up within the polling interval.
@@ -2505,6 +2569,19 @@ --vfs-cache-mode full
When reading a file rclone will read --buffer-size plus --vfs-read-ahead bytes ahead. The --buffer-size is buffered in memory whereas the --vfs-read-ahead is buffered on disk.
When using this mode it is recommended that --buffer-size is not set too large and --vfs-read-ahead is set large if required.
IMPORTANT not all file systems support sparse files. In particular FAT/exFAT do not. Rclone will perform very badly if the cache directory is on a filesystem which doesn't support sparse files and it will log an ERROR message if one is detected.
+Fingerprinting
+Various parts of the VFS use fingerprinting to see if a local file copy has changed relative to a remote file. Fingerprints are made from:
+
+- size
+- modification time
+- hash
+
+where available on an object.
+On some backends some of these attributes are slow to read (they take an extra API call per object, or extra work per object).
+For example hash is slow with the local and sftp backends as they have to read the entire file and hash it, and modtime is slow with the s3, swift, ftp and qinqstor backends because they need to do an extra API call to fetch it.
+If you use the --vfs-fast-fingerprint flag then rclone will not include the slow operations in the fingerprint. This makes the fingerprinting less accurate but much faster and will improve the opening time of cached files.
+If you are running a vfs cache over local, s3 or swift backends then using this flag is recommended.
+Note that if you change the value of this flag, the fingerprints of the files in the cache may be invalidated and the files will need to be downloaded again.
VFS Chunked Reading
When rclone reads files from a remote it reads them in chunks. This means that rather than requesting the whole file rclone reads the chunk specified. This can reduce the used download quota for some remotes by requesting only chunks from the remote that are actually read, at the cost of an increased number of requests.
These flags control the chunking:
@@ -2519,20 +2596,23 @@
--no-checksum Don't compare checksums on up/download.
--no-modtime Don't read/write the modification time (can speed things up).
--no-seek Don't allow seeking in files.
---read-only Mount read-only.
+--read-only Only allow read-only access.
Sometimes rclone is delivered reads or writes out of order. Rather than seeking rclone will wait a short time for the in sequence read or write to come in. These flags only come into effect when not using an on disk cache file.
--vfs-read-wait duration Time to wait for in-sequence read before seeking (default 20ms)
--vfs-write-wait duration Time to wait for in-sequence write before giving error (default 1s)
-When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from cache (the related global flag --checkers have no effect on mount).
+When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from the cache (the related global flag --checkers has no effect on the VFS).
--transfers int Number of file transfers to run in parallel (default 4)
VFS Case Sensitivity
Linux file systems are case-sensitive: two files can differ only by case, and the exact case must be used when opening a file.
File systems in modern Windows are case-insensitive but case-preserving: although existing files can be opened using any case, the exact case used to create the file is preserved and available for programs to query. It is not allowed for two files in the same directory to differ only by case.
Usually file systems on macOS are case-insensitive. It is possible to make macOS file systems case-sensitive but that is not the default.
-The --vfs-case-insensitive mount flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the mounted file system as-is. If the flag is "true" (or appears without a value on command line), rclone may perform a "fixup" as explained below.
-The user may specify a file name to open/delete/rename/etc with a case different than what is stored on mounted file system. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by an underlying mounted file system.
-Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system mounted by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
+The --vfs-case-insensitive VFS flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the remote as-is. If the flag is "true" (or appears without a value on the command line), rclone may perform a "fixup" as explained below.
+The user may specify a file name to open/delete/rename/etc with a case different than what is stored on the remote. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by the underlying remote.
+Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system presented by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
If the flag is not provided on the command line, then its default value depends on the operating system where rclone runs: "true" on Windows and macOS, "false" otherwise. If the flag is provided without a value, then it is "true".
+VFS Disk Options
+This flag allows you to manually set the statistics about the filing system. It can be useful when those statistics cannot be read correctly automatically.
+--vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used. If you need this information to be available when running df on the filesystem, then pass the flag --vfs-used-is-size to rclone. With this flag set, instead of relying on the backend to report this information, rclone will scan the whole remote similar to rclone size and compute the total used space itself.
WARNING. Contrary to rclone size, this flag ignores filters so that the result is accurate. However, this is very inefficient and may cost lots of API calls resulting in extra charges. Use it as a last resort and only with caching.
@@ -2567,7 +2647,7 @@ Options
--noapplexattr Ignore all "com.apple.*" extended attributes (supported on OSX only)
-o, --option stringArray Option for libfuse/WinFsp (repeat if required)
--poll-interval duration Time to wait between polling for changes, must be smaller than dir-cache-time and only on supported remotes (set 0 to disable) (default 1m0s)
- --read-only Mount read-only
+ --read-only Only allow read-only access
--socket-addr string Address <host:port> or absolute path (default: /run/docker/plugins/rclone.sock)
--socket-gid int GID for unix socket (default: current process GID) (default 1000)
--uid uint32 Override the uid field set by the filesystem (not supported on Windows) (default 1000)
@@ -2577,6 +2657,8 @@ Options
--vfs-cache-mode CacheMode Cache mode off|minimal|writes|full (default off)
--vfs-cache-poll-interval duration Interval to poll the cache for stale objects (default 1m0s)
--vfs-case-insensitive If a file name not found, find a case insensitive match
+ --vfs-disk-space-total-size SizeSuffix Specify the total space of disk (default off)
+ --vfs-fast-fingerprint Use fast (less accurate) fingerprints for change detection
--vfs-read-ahead SizeSuffix Extra read ahead over --buffer-size when using cache-mode full
--vfs-read-chunk-size SizeSuffix Read the source objects in chunks (default 128Mi)
--vfs-read-chunk-size-limit SizeSuffix If greater than --vfs-read-chunk-size, double the chunk size after each chunk read, until the limit is reached ('off' is unlimited) (default off)
@@ -2593,8 +2675,8 @@ SEE ALSO
rclone serve ftp
Serve remote:path over FTP.
-Synopsis
-rclone serve ftp implements a basic ftp server to serve the remote over FTP protocol. This can be viewed with a ftp client or you can make a remote of type ftp to read and write it.
+Synopsis
+Run a basic FTP server to serve a remote over FTP protocol. This can be viewed with a FTP client or you can make a remote of type FTP to read and write it.
Server options
Use --addr to specify which IP address and port the server should listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs. By default it only listens on localhost. You can use port :0 to let the OS choose an available port.
If you set --addr to listen on a public or LAN accessible IP address then using Authentication is advised - see the next section for info.
@@ -2606,7 +2688,7 @@ VFS - Virtual File System
Cloud storage objects have lots of properties which aren't like disk files - you can't extend them or write to the middle of them, so the VFS layer has to deal with that. Because there is no one right way of doing this there are various options explained below.
The VFS layer also implements a directory cache - this caches info about files and directories (but not the data) in memory.
VFS Directory Cache
-Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the mount will appear immediately or invalidate the cache.
+Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the VFS will appear immediately or invalidate the cache.
--dir-cache-time duration Time to cache directory entries for (default 5m0s)
--poll-interval duration Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable (default 1m0s)
However, changes made directly on the cloud storage by the web interface or a different copy of rclone will only be picked up once the directory cache expires if the backend configured does not support polling for changes. If the backend supports polling, changes will be picked up within the polling interval.
@@ -2669,6 +2751,19 @@ --vfs-cache-mode full
When reading a file rclone will read --buffer-size plus --vfs-read-ahead bytes ahead. The --buffer-size is buffered in memory whereas the --vfs-read-ahead is buffered on disk.
When using this mode it is recommended that --buffer-size is not set too large and --vfs-read-ahead is set large if required.
IMPORTANT not all file systems support sparse files. In particular FAT/exFAT do not. Rclone will perform very badly if the cache directory is on a filesystem which doesn't support sparse files and it will log an ERROR message if one is detected.
+Fingerprinting
+Various parts of the VFS use fingerprinting to see if a local file copy has changed relative to a remote file. Fingerprints are made from:
+
+- size
+- modification time
+- hash
+
+where available on an object.
+On some backends some of these attributes are slow to read (they take an extra API call per object, or extra work per object).
+For example hash is slow with the local and sftp backends as they have to read the entire file and hash it, and modtime is slow with the s3, swift, ftp and qinqstor backends because they need to do an extra API call to fetch it.
+If you use the --vfs-fast-fingerprint flag then rclone will not include the slow operations in the fingerprint. This makes the fingerprinting less accurate but much faster and will improve the opening time of cached files.
+If you are running a vfs cache over local, s3 or swift backends then using this flag is recommended.
+Note that if you change the value of this flag, the fingerprints of the files in the cache may be invalidated and the files will need to be downloaded again.
VFS Chunked Reading
When rclone reads files from a remote it reads them in chunks. This means that rather than requesting the whole file rclone reads the chunk specified. This can reduce the used download quota for some remotes by requesting only chunks from the remote that are actually read, at the cost of an increased number of requests.
These flags control the chunking:
@@ -2683,20 +2778,23 @@
--no-checksum Don't compare checksums on up/download.
--no-modtime Don't read/write the modification time (can speed things up).
--no-seek Don't allow seeking in files.
---read-only Mount read-only.
+--read-only Only allow read-only access.
Sometimes rclone is delivered reads or writes out of order. Rather than seeking rclone will wait a short time for the in sequence read or write to come in. These flags only come into effect when not using an on disk cache file.
--vfs-read-wait duration Time to wait for in-sequence read before seeking (default 20ms)
--vfs-write-wait duration Time to wait for in-sequence write before giving error (default 1s)
-When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from cache (the related global flag --checkers have no effect on mount).
+When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from the cache (the related global flag --checkers has no effect on the VFS).
--transfers int Number of file transfers to run in parallel (default 4)
VFS Case Sensitivity
Linux file systems are case-sensitive: two files can differ only by case, and the exact case must be used when opening a file.
File systems in modern Windows are case-insensitive but case-preserving: although existing files can be opened using any case, the exact case used to create the file is preserved and available for programs to query. It is not allowed for two files in the same directory to differ only by case.
Usually file systems on macOS are case-insensitive. It is possible to make macOS file systems case-sensitive but that is not the default.
-The --vfs-case-insensitive mount flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the mounted file system as-is. If the flag is "true" (or appears without a value on command line), rclone may perform a "fixup" as explained below.
-The user may specify a file name to open/delete/rename/etc with a case different than what is stored on mounted file system. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by an underlying mounted file system.
-Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system mounted by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
+The --vfs-case-insensitive VFS flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the remote as-is. If the flag is "true" (or appears without a value on the command line), rclone may perform a "fixup" as explained below.
+The user may specify a file name to open/delete/rename/etc with a case different than what is stored on the remote. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by the underlying remote.
+Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system presented by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
If the flag is not provided on the command line, then its default value depends on the operating system where rclone runs: "true" on Windows and macOS, "false" otherwise. If the flag is provided without a value, then it is "true".
+VFS Disk Options
+This flag allows you to manually set the statistics about the filing system. It can be useful when those statistics cannot be read correctly automatically.
+--vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used. If you need this information to be available when running df on the filesystem, then pass the flag --vfs-used-is-size to rclone. With this flag set, instead of relying on the backend to report this information, rclone will scan the whole remote similar to rclone size and compute the total used space itself.
WARNING. Contrary to rclone size, this flag ignores filters so that the result is accurate. However, this is very inefficient and may cost lots of API calls resulting in extra charges. Use it as a last resort and only with caching.
@@ -2748,7 +2846,7 @@ Options
--passive-port string Passive port range to use (default "30000-32000")
--poll-interval duration Time to wait between polling for changes, must be smaller than dir-cache-time and only on supported remotes (set 0 to disable) (default 1m0s)
--public-ip string Public IP address to advertise for passive connections
- --read-only Mount read-only
+ --read-only Only allow read-only access
--uid uint32 Override the uid field set by the filesystem (not supported on Windows) (default 1000)
--umask int Override the permission bits set by the filesystem (not supported on Windows) (default 2)
--user string User name for authentication (default "anonymous")
@@ -2757,6 +2855,8 @@ Options
--vfs-cache-mode CacheMode Cache mode off|minimal|writes|full (default off)
--vfs-cache-poll-interval duration Interval to poll the cache for stale objects (default 1m0s)
--vfs-case-insensitive If a file name not found, find a case insensitive match
+ --vfs-disk-space-total-size SizeSuffix Specify the total space of disk (default off)
+ --vfs-fast-fingerprint Use fast (less accurate) fingerprints for change detection
--vfs-read-ahead SizeSuffix Extra read ahead over --buffer-size when using cache-mode full
--vfs-read-chunk-size SizeSuffix Read the source objects in chunks (default 128Mi)
--vfs-read-chunk-size-limit SizeSuffix If greater than --vfs-read-chunk-size, double the chunk size after each chunk read, until the limit is reached ('off' is unlimited) (default off)
@@ -2771,22 +2871,22 @@ SEE ALSO
rclone serve http
Serve the remote over HTTP.
-Synopsis
-rclone serve http implements a basic web server to serve the remote over HTTP. This can be viewed in a web browser or you can make a remote of type http read from it.
-You can use the filter flags (e.g. --include, --exclude) to control what is served.
-The server will log errors. Use -v to see access logs.
---bwlimit will be respected for file transfers. Use --stats to control the stats printing.
+Synopsis
+Run a basic web server to serve a remote over HTTP. This can be viewed in a web browser or you can make a remote of type http read from it.
+You can use the filter flags (e.g. --include, --exclude) to control what is served.
+The server will log errors. Use -v to see access logs.
+--bwlimit will be respected for file transfers. Use --stats to control the stats printing.
Server options
-Use --addr to specify which IP address and port the server should listen on, eg --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs. By default it only listens on localhost. You can use port :0 to let the OS choose an available port.
-If you set --addr to listen on a public or LAN accessible IP address then using Authentication is advised - see the next section for info.
---server-read-timeout and --server-write-timeout can be used to control the timeouts on the server. Note that this is the total time for a transfer.
---max-header-bytes controls the maximum number of bytes the server will accept in the HTTP header.
---baseurl controls the URL prefix that rclone serves from. By default rclone will serve from the root. If you used --baseurl "/rclone" then rclone would serve from a URL starting with "/rclone/". This is useful if you wish to proxy rclone serve. Rclone automatically inserts leading and trailing "/" on --baseurl, so --baseurl "rclone", --baseurl "/rclone" and --baseurl "/rclone/" are all treated identically.
+Use --addr to specify which IP address and port the server should listen on, eg --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs. By default it only listens on localhost. You can use port :0 to let the OS choose an available port.
+If you set --addr to listen on a public or LAN accessible IP address then using Authentication is advised - see the next section for info.
+--server-read-timeout and --server-write-timeout can be used to control the timeouts on the server. Note that this is the total time for a transfer.
+--max-header-bytes controls the maximum number of bytes the server will accept in the HTTP header.
+--baseurl controls the URL prefix that rclone serves from. By default rclone will serve from the root. If you used --baseurl "/rclone" then rclone would serve from a URL starting with "/rclone/". This is useful if you wish to proxy rclone serve. Rclone automatically inserts leading and trailing "/" on --baseurl, so --baseurl "rclone", --baseurl "/rclone" and --baseurl "/rclone/" are all treated identically.
SSL/TLS
-By default this will serve over http. If you want you can serve over https. You will need to supply the --cert and --key flags. If you wish to do client side certificate validation then you will need to supply --client-ca also.
---cert should be a either a PEM encoded certificate or a concatenation of that with the CA certificate. --key should be the PEM encoded private key and --client-ca should be the PEM encoded client certificate authority certificate.
+By default this will serve over http. If you want you can serve over https. You will need to supply the --cert and --key flags. If you wish to do client side certificate validation then you will need to supply --client-ca also.
+--cert should be a either a PEM encoded certificate or a concatenation of that with the CA certificate. --key should be the PEM encoded private key and --client-ca should be the PEM encoded client certificate authority certificate.
Template
---template allows a user to specify a custom markup template for http and webdav serve functions. The server exports the following markup to be used within the template to server pages:
+--template allows a user to specify a custom markup template for HTTP and WebDAV serve functions. The server exports the following markup to be used within the template to server pages:
@@ -2867,21 +2967,21 @@ Template
Authentication
By default this will serve files without needing a login.
-You can either use an htpasswd file which can take lots of users, or set a single username and password with the --user and --pass flags.
-Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is in standard apache format and supports MD5, SHA1 and BCrypt for basic authentication. Bcrypt is recommended.
+You can either use an htpasswd file which can take lots of users, or set a single username and password with the --user and --pass flags.
+Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is in standard apache format and supports MD5, SHA1 and BCrypt for basic authentication. Bcrypt is recommended.
To create an htpasswd file:
touch htpasswd
htpasswd -B htpasswd user
htpasswd -B htpasswd anotherUser
The password file can be updated while rclone is running.
-Use --realm to set the authentication realm.
-Use --salt to change the password hashing salt from the default.
+Use --realm to set the authentication realm.
+Use --salt to change the password hashing salt from the default.
VFS - Virtual File System
This command uses the VFS layer. This adapts the cloud storage objects that rclone uses into something which looks much more like a disk filing system.
Cloud storage objects have lots of properties which aren't like disk files - you can't extend them or write to the middle of them, so the VFS layer has to deal with that. Because there is no one right way of doing this there are various options explained below.
The VFS layer also implements a directory cache - this caches info about files and directories (but not the data) in memory.
VFS Directory Cache
-Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the mount will appear immediately or invalidate the cache.
+Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the VFS will appear immediately or invalidate the cache.
--dir-cache-time duration Time to cache directory entries for (default 5m0s)
--poll-interval duration Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable (default 1m0s)
However, changes made directly on the cloud storage by the web interface or a different copy of rclone will only be picked up once the directory cache expires if the backend configured does not support polling for changes. If the backend supports polling, changes will be picked up within the polling interval.
@@ -2944,6 +3044,19 @@ --vfs-cache-mode full
When reading a file rclone will read --buffer-size plus --vfs-read-ahead bytes ahead. The --buffer-size is buffered in memory whereas the --vfs-read-ahead is buffered on disk.
When using this mode it is recommended that --buffer-size is not set too large and --vfs-read-ahead is set large if required.
IMPORTANT not all file systems support sparse files. In particular FAT/exFAT do not. Rclone will perform very badly if the cache directory is on a filesystem which doesn't support sparse files and it will log an ERROR message if one is detected.
+Fingerprinting
+Various parts of the VFS use fingerprinting to see if a local file copy has changed relative to a remote file. Fingerprints are made from:
+
+- size
+- modification time
+- hash
+
+where available on an object.
+On some backends some of these attributes are slow to read (they take an extra API call per object, or extra work per object).
+For example hash is slow with the local and sftp backends as they have to read the entire file and hash it, and modtime is slow with the s3, swift, ftp and qinqstor backends because they need to do an extra API call to fetch it.
+If you use the --vfs-fast-fingerprint flag then rclone will not include the slow operations in the fingerprint. This makes the fingerprinting less accurate but much faster and will improve the opening time of cached files.
+If you are running a vfs cache over local, s3 or swift backends then using this flag is recommended.
+Note that if you change the value of this flag, the fingerprints of the files in the cache may be invalidated and the files will need to be downloaded again.
VFS Chunked Reading
When rclone reads files from a remote it reads them in chunks. This means that rather than requesting the whole file rclone reads the chunk specified. This can reduce the used download quota for some remotes by requesting only chunks from the remote that are actually read, at the cost of an increased number of requests.
These flags control the chunking:
@@ -2958,20 +3071,23 @@
--no-checksum Don't compare checksums on up/download.
--no-modtime Don't read/write the modification time (can speed things up).
--no-seek Don't allow seeking in files.
---read-only Mount read-only.
+--read-only Only allow read-only access.
Sometimes rclone is delivered reads or writes out of order. Rather than seeking rclone will wait a short time for the in sequence read or write to come in. These flags only come into effect when not using an on disk cache file.
--vfs-read-wait duration Time to wait for in-sequence read before seeking (default 20ms)
--vfs-write-wait duration Time to wait for in-sequence write before giving error (default 1s)
-When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from cache (the related global flag --checkers have no effect on mount).
+When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from the cache (the related global flag --checkers has no effect on the VFS).
--transfers int Number of file transfers to run in parallel (default 4)
VFS Case Sensitivity
Linux file systems are case-sensitive: two files can differ only by case, and the exact case must be used when opening a file.
File systems in modern Windows are case-insensitive but case-preserving: although existing files can be opened using any case, the exact case used to create the file is preserved and available for programs to query. It is not allowed for two files in the same directory to differ only by case.
Usually file systems on macOS are case-insensitive. It is possible to make macOS file systems case-sensitive but that is not the default.
-The --vfs-case-insensitive mount flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the mounted file system as-is. If the flag is "true" (or appears without a value on command line), rclone may perform a "fixup" as explained below.
-The user may specify a file name to open/delete/rename/etc with a case different than what is stored on mounted file system. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by an underlying mounted file system.
-Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system mounted by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
+The --vfs-case-insensitive VFS flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the remote as-is. If the flag is "true" (or appears without a value on the command line), rclone may perform a "fixup" as explained below.
+The user may specify a file name to open/delete/rename/etc with a case different than what is stored on the remote. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by the underlying remote.
+Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system presented by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
If the flag is not provided on the command line, then its default value depends on the operating system where rclone runs: "true" on Windows and macOS, "false" otherwise. If the flag is provided without a value, then it is "true".
+VFS Disk Options
+This flag allows you to manually set the statistics about the filing system. It can be useful when those statistics cannot be read correctly automatically.
+--vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used. If you need this information to be available when running df on the filesystem, then pass the flag --vfs-used-is-size to rclone. With this flag set, instead of relying on the backend to report this information, rclone will scan the whole remote similar to rclone size and compute the total used space itself.
WARNING. Contrary to rclone size, this flag ignores filters so that the result is accurate. However, this is very inefficient and may cost lots of API calls resulting in extra charges. Use it as a last resort and only with caching.
@@ -2994,7 +3110,7 @@ Options
--no-seek Don't allow seeking in files
--pass string Password for authentication
--poll-interval duration Time to wait between polling for changes, must be smaller than dir-cache-time and only on supported remotes (set 0 to disable) (default 1m0s)
- --read-only Mount read-only
+ --read-only Only allow read-only access
--realm string Realm for authentication
--salt string Password hashing salt (default "dlPL2MqE")
--server-read-timeout duration Timeout for server reading data (default 1h0m0s)
@@ -3008,6 +3124,8 @@ Options
--vfs-cache-mode CacheMode Cache mode off|minimal|writes|full (default off)
--vfs-cache-poll-interval duration Interval to poll the cache for stale objects (default 1m0s)
--vfs-case-insensitive If a file name not found, find a case insensitive match
+ --vfs-disk-space-total-size SizeSuffix Specify the total space of disk (default off)
+ --vfs-fast-fingerprint Use fast (less accurate) fingerprints for change detection
--vfs-read-ahead SizeSuffix Extra read ahead over --buffer-size when using cache-mode full
--vfs-read-chunk-size SizeSuffix Read the source objects in chunks (default 128Mi)
--vfs-read-chunk-size-limit SizeSuffix If greater than --vfs-read-chunk-size, double the chunk size after each chunk read, until the limit is reached ('off' is unlimited) (default off)
@@ -3022,20 +3140,20 @@ SEE ALSO
rclone serve restic
Serve the remote for restic's REST API.
-Synopsis
-rclone serve restic implements restic's REST backend API over HTTP. This allows restic to use rclone as a data storage mechanism for cloud providers that restic does not support directly.
+Synopsis
+Run a basic web server to serve a remove over restic's REST backend API over HTTP. This allows restic to use rclone as a data storage mechanism for cloud providers that restic does not support directly.
Restic is a command-line program for doing backups.
The server will log errors. Use -v to see access logs.
---bwlimit will be respected for file transfers. Use --stats to control the stats printing.
+--bwlimit will be respected for file transfers. Use --stats to control the stats printing.
Setting up rclone for use by restic
First set up a remote for your chosen cloud provider.
Once you have set up the remote, check it is working with, for example "rclone lsd remote:". You may have called the remote something other than "remote:" - just substitute whatever you called it in the following instructions.
Now start the rclone restic server
rclone serve restic -v remote:backup
Where you can replace "backup" in the above by whatever path in the remote you wish to use.
-By default this will serve on "localhost:8080" you can change this with use of the "--addr" flag.
+By default this will serve on "localhost:8080" you can change this with use of the --addr flag.
You might wish to start this server on boot.
-Adding --cache-objects=false will cause rclone to stop caching objects returned from the List call. Caching is normally desirable as it speeds up downloading objects, saves transactions and uses very little memory.
+Adding --cache-objects=false will cause rclone to stop caching objects returned from the List call. Caching is normally desirable as it speeds up downloading objects, saves transactions and uses very little memory.
Setting up restic to use rclone
Now you can follow the restic instructions on setting up restic.
Note that you will need restic 0.8.2 or later to interoperate with rclone.
@@ -3062,14 +3180,14 @@ Multiple repositories
$ export RESTIC_REPOSITORY=rest:http://localhost:8080/user2repo/
# backup user2 stuff
Private repositories
-The "--private-repos" flag can be used to limit users to repositories starting with a path of /<username>/.
+The--private-repos flag can be used to limit users to repositories starting with a path of /<username>/.
Server options
-Use --addr to specify which IP address and port the server should listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs. By default it only listens on localhost. You can use port :0 to let the OS choose an available port.
-If you set --addr to listen on a public or LAN accessible IP address then using Authentication is advised - see the next section for info.
---server-read-timeout and --server-write-timeout can be used to control the timeouts on the server. Note that this is the total time for a transfer.
---max-header-bytes controls the maximum number of bytes the server will accept in the HTTP header.
---baseurl controls the URL prefix that rclone serves from. By default rclone will serve from the root. If you used --baseurl "/rclone" then rclone would serve from a URL starting with "/rclone/". This is useful if you wish to proxy rclone serve. Rclone automatically inserts leading and trailing "/" on --baseurl, so --baseurl "rclone", --baseurl "/rclone" and --baseurl "/rclone/" are all treated identically.
---template allows a user to specify a custom markup template for http and webdav serve functions. The server exports the following markup to be used within the template to server pages:
+Use --addr to specify which IP address and port the server should listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs. By default it only listens on localhost. You can use port :0 to let the OS choose an available port.
+If you set --addr to listen on a public or LAN accessible IP address then using Authentication is advised - see the next section for info.
+--server-read-timeout and --server-write-timeout can be used to control the timeouts on the server. Note that this is the total time for a transfer.
+--max-header-bytes controls the maximum number of bytes the server will accept in the HTTP header.
+--baseurl controls the URL prefix that rclone serves from. By default rclone will serve from the root. If you used --baseurl "/rclone" then rclone would serve from a URL starting with "/rclone/". This is useful if you wish to proxy rclone serve. Rclone automatically inserts leading and trailing "/" on --baseurl, so --baseurl "rclone", --baseurl "/rclone" and --baseurl "/rclone/" are all treated identically.
+--template allows a user to specify a custom markup template for HTTP and WebDAV serve functions. The server exports the following markup to be used within the template to server pages:
@@ -3150,17 +3268,17 @@ Server options
Authentication
By default this will serve files without needing a login.
-You can either use an htpasswd file which can take lots of users, or set a single username and password with the --user and --pass flags.
-Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is in standard apache format and supports MD5, SHA1 and BCrypt for basic authentication. Bcrypt is recommended.
+You can either use an htpasswd file which can take lots of users, or set a single username and password with the --user and --pass flags.
+Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is in standard apache format and supports MD5, SHA1 and BCrypt for basic authentication. Bcrypt is recommended.
To create an htpasswd file:
touch htpasswd
htpasswd -B htpasswd user
htpasswd -B htpasswd anotherUser
The password file can be updated while rclone is running.
-Use --realm to set the authentication realm.
+Use --realm to set the authentication realm.
SSL/TLS
-By default this will serve over http. If you want you can serve over https. You will need to supply the --cert and --key flags. If you wish to do client side certificate validation then you will need to supply --client-ca also.
---cert should be either a PEM encoded certificate or a concatenation of that with the CA certificate. --key should be the PEM encoded private key and --client-ca should be the PEM encoded client certificate authority certificate.
+By default this will serve over HTTP. If you want you can serve over HTTPS. You will need to supply the --cert and --key flags. If you wish to do client side certificate validation then you will need to supply --client-ca also.
+--cert should be either a PEM encoded certificate or a concatenation of that with the CA certificate. --key should be the PEM encoded private key and --client-ca should be the PEM encoded client certificate authority certificate.
rclone serve restic remote:path [flags]
Options
--addr string IPaddress:Port or :Port to bind server to (default "localhost:8080")
@@ -3188,26 +3306,26 @@ SEE ALSO
rclone serve sftp
Serve the remote over SFTP.
-Synopsis
-rclone serve sftp implements an SFTP server to serve the remote over SFTP. This can be used with an SFTP client or you can make a remote of type sftp to use with it.
-You can use the filter flags (e.g. --include, --exclude) to control what is served.
-The server will log errors. Use -v to see access logs.
---bwlimit will be respected for file transfers. Use --stats to control the stats printing.
-You must provide some means of authentication, either with --user/--pass, an authorized keys file (specify location with --authorized-keys - the default is the same as ssh), an --auth-proxy, or set the --no-auth flag for no authentication when logging in.
+Synopsis
+Run a SFTP server to serve a remote over SFTP. This can be used with an SFTP client or you can make a remote of type sftp to use with it.
+You can use the filter flags (e.g. --include, --exclude) to control what is served.
+The server will log errors. Use -v to see access logs.
+--bwlimit will be respected for file transfers. Use --stats to control the stats printing.
+You must provide some means of authentication, either with --user/--pass, an authorized keys file (specify location with --authorized-keys - the default is the same as ssh), an --auth-proxy, or set the --no-auth flag for no authentication when logging in.
Note that this also implements a small number of shell commands so that it can provide md5sum/sha1sum/df information for the rclone sftp backend. This means that is can support SHA1SUMs, MD5SUMs and the about command when paired with the rclone sftp backend.
-If you don't supply a host --key then rclone will generate rsa, ecdsa and ed25519 variants, and cache them for later use in rclone's cache directory (see "rclone help flags cache-dir") in the "serve-sftp" directory.
-By default the server binds to localhost:2022 - if you want it to be reachable externally then supply "--addr :2022" for example.
-Note that the default of "--vfs-cache-mode off" is fine for the rclone sftp backend, but it may not be with other SFTP clients.
-If --stdio is specified, rclone will serve SFTP over stdio, which can be used with sshd via ~/.ssh/authorized_keys, for example:
+If you don't supply a host --key then rclone will generate rsa, ecdsa and ed25519 variants, and cache them for later use in rclone's cache directory (see rclone help flags cache-dir) in the "serve-sftp" directory.
+By default the server binds to localhost:2022 - if you want it to be reachable externally then supply --addr :2022 for example.
+Note that the default of --vfs-cache-mode off is fine for the rclone sftp backend, but it may not be with other SFTP clients.
+If --stdio is specified, rclone will serve SFTP over stdio, which can be used with sshd via ~/.ssh/authorized_keys, for example:
restrict,command="rclone serve sftp --stdio ./photos" ssh-rsa ...
-On the client you need to set "--transfers 1" when using --stdio. Otherwise multiple instances of the rclone server are started by OpenSSH which can lead to "corrupted on transfer" errors. This is the case because the client chooses indiscriminately which server to send commands to while the servers all have different views of the state of the filing system.
-The "restrict" in authorized_keys prevents SHA1SUMs and MD5SUMs from beeing used. Omitting "restrict" and using --sftp-path-override to enable checksumming is possible but less secure and you could use the SFTP server provided by OpenSSH in this case.
+On the client you need to set --transfers 1 when using --stdio. Otherwise multiple instances of the rclone server are started by OpenSSH which can lead to "corrupted on transfer" errors. This is the case because the client chooses indiscriminately which server to send commands to while the servers all have different views of the state of the filing system.
+The "restrict" in authorized_keys prevents SHA1SUMs and MD5SUMs from beeing used. Omitting "restrict" and using --sftp-path-override to enable checksumming is possible but less secure and you could use the SFTP server provided by OpenSSH in this case.
VFS - Virtual File System
This command uses the VFS layer. This adapts the cloud storage objects that rclone uses into something which looks much more like a disk filing system.
Cloud storage objects have lots of properties which aren't like disk files - you can't extend them or write to the middle of them, so the VFS layer has to deal with that. Because there is no one right way of doing this there are various options explained below.
The VFS layer also implements a directory cache - this caches info about files and directories (but not the data) in memory.
VFS Directory Cache
-Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the mount will appear immediately or invalidate the cache.
+Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the VFS will appear immediately or invalidate the cache.
--dir-cache-time duration Time to cache directory entries for (default 5m0s)
--poll-interval duration Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable (default 1m0s)
However, changes made directly on the cloud storage by the web interface or a different copy of rclone will only be picked up once the directory cache expires if the backend configured does not support polling for changes. If the backend supports polling, changes will be picked up within the polling interval.
@@ -3270,6 +3388,19 @@ --vfs-cache-mode full
When reading a file rclone will read --buffer-size plus --vfs-read-ahead bytes ahead. The --buffer-size is buffered in memory whereas the --vfs-read-ahead is buffered on disk.
When using this mode it is recommended that --buffer-size is not set too large and --vfs-read-ahead is set large if required.
IMPORTANT not all file systems support sparse files. In particular FAT/exFAT do not. Rclone will perform very badly if the cache directory is on a filesystem which doesn't support sparse files and it will log an ERROR message if one is detected.
+Fingerprinting
+Various parts of the VFS use fingerprinting to see if a local file copy has changed relative to a remote file. Fingerprints are made from:
+
+- size
+- modification time
+- hash
+
+where available on an object.
+On some backends some of these attributes are slow to read (they take an extra API call per object, or extra work per object).
+For example hash is slow with the local and sftp backends as they have to read the entire file and hash it, and modtime is slow with the s3, swift, ftp and qinqstor backends because they need to do an extra API call to fetch it.
+If you use the --vfs-fast-fingerprint flag then rclone will not include the slow operations in the fingerprint. This makes the fingerprinting less accurate but much faster and will improve the opening time of cached files.
+If you are running a vfs cache over local, s3 or swift backends then using this flag is recommended.
+Note that if you change the value of this flag, the fingerprints of the files in the cache may be invalidated and the files will need to be downloaded again.
VFS Chunked Reading
When rclone reads files from a remote it reads them in chunks. This means that rather than requesting the whole file rclone reads the chunk specified. This can reduce the used download quota for some remotes by requesting only chunks from the remote that are actually read, at the cost of an increased number of requests.
These flags control the chunking:
@@ -3284,20 +3415,23 @@
--no-checksum Don't compare checksums on up/download.
--no-modtime Don't read/write the modification time (can speed things up).
--no-seek Don't allow seeking in files.
---read-only Mount read-only.
+--read-only Only allow read-only access.
Sometimes rclone is delivered reads or writes out of order. Rather than seeking rclone will wait a short time for the in sequence read or write to come in. These flags only come into effect when not using an on disk cache file.
--vfs-read-wait duration Time to wait for in-sequence read before seeking (default 20ms)
--vfs-write-wait duration Time to wait for in-sequence write before giving error (default 1s)
-When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from cache (the related global flag --checkers have no effect on mount).
+When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from the cache (the related global flag --checkers has no effect on the VFS).
--transfers int Number of file transfers to run in parallel (default 4)
VFS Case Sensitivity
Linux file systems are case-sensitive: two files can differ only by case, and the exact case must be used when opening a file.
File systems in modern Windows are case-insensitive but case-preserving: although existing files can be opened using any case, the exact case used to create the file is preserved and available for programs to query. It is not allowed for two files in the same directory to differ only by case.
Usually file systems on macOS are case-insensitive. It is possible to make macOS file systems case-sensitive but that is not the default.
-The --vfs-case-insensitive mount flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the mounted file system as-is. If the flag is "true" (or appears without a value on command line), rclone may perform a "fixup" as explained below.
-The user may specify a file name to open/delete/rename/etc with a case different than what is stored on mounted file system. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by an underlying mounted file system.
-Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system mounted by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
+The --vfs-case-insensitive VFS flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the remote as-is. If the flag is "true" (or appears without a value on the command line), rclone may perform a "fixup" as explained below.
+The user may specify a file name to open/delete/rename/etc with a case different than what is stored on the remote. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by the underlying remote.
+Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system presented by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
If the flag is not provided on the command line, then its default value depends on the operating system where rclone runs: "true" on Windows and macOS, "false" otherwise. If the flag is provided without a value, then it is "true".
+VFS Disk Options
+This flag allows you to manually set the statistics about the filing system. It can be useful when those statistics cannot be read correctly automatically.
+--vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used. If you need this information to be available when running df on the filesystem, then pass the flag --vfs-used-is-size to rclone. With this flag set, instead of relying on the backend to report this information, rclone will scan the whole remote similar to rclone size and compute the total used space itself.
WARNING. Contrary to rclone size, this flag ignores filters so that the result is accurate. However, this is very inefficient and may cost lots of API calls resulting in extra charges. Use it as a last resort and only with caching.
@@ -3348,7 +3482,7 @@ Options
--no-seek Don't allow seeking in files
--pass string Password for authentication
--poll-interval duration Time to wait between polling for changes, must be smaller than dir-cache-time and only on supported remotes (set 0 to disable) (default 1m0s)
- --read-only Mount read-only
+ --read-only Only allow read-only access
--stdio Run an sftp server on run stdin/stdout
--uid uint32 Override the uid field set by the filesystem (not supported on Windows) (default 1000)
--umask int Override the permission bits set by the filesystem (not supported on Windows) (default 2)
@@ -3358,6 +3492,8 @@ Options
--vfs-cache-mode CacheMode Cache mode off|minimal|writes|full (default off)
--vfs-cache-poll-interval duration Interval to poll the cache for stale objects (default 1m0s)
--vfs-case-insensitive If a file name not found, find a case insensitive match
+ --vfs-disk-space-total-size SizeSuffix Specify the total space of disk (default off)
+ --vfs-fast-fingerprint Use fast (less accurate) fingerprints for change detection
--vfs-read-ahead SizeSuffix Extra read ahead over --buffer-size when using cache-mode full
--vfs-read-chunk-size SizeSuffix Read the source objects in chunks (default 128Mi)
--vfs-read-chunk-size-limit SizeSuffix If greater than --vfs-read-chunk-size, double the chunk size after each chunk read, until the limit is reached ('off' is unlimited) (default off)
@@ -3371,21 +3507,20 @@ SEE ALSO
rclone serve - Serve a remote over a protocol.
rclone serve webdav
-Serve remote:path over webdav.
-Synopsis
-rclone serve webdav implements a basic webdav server to serve the remote over HTTP via the webdav protocol. This can be viewed with a webdav client, through a web browser, or you can make a remote of type webdav to read and write it.
-Webdav options
+Serve remote:path over WebDAV.
+Synopsis
+Run a basic WebDAV server to serve a remote over HTTP via the WebDAV protocol. This can be viewed with a WebDAV client, through a web browser, or you can make a remote of type WebDAV to read and write it.
+WebDAV options
--etag-hash
This controls the ETag header. Without this flag the ETag will be based on the ModTime and Size of the object.
-If this flag is set to "auto" then rclone will choose the first supported hash on the backend or you can use a named hash such as "MD5" or "SHA-1".
-Use "rclone hashsum" to see the full list.
+If this flag is set to "auto" then rclone will choose the first supported hash on the backend or you can use a named hash such as "MD5" or "SHA-1". Use the hashsum command to see the full list.
Server options
-Use --addr to specify which IP address and port the server should listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs. By default it only listens on localhost. You can use port :0 to let the OS choose an available port.
-If you set --addr to listen on a public or LAN accessible IP address then using Authentication is advised - see the next section for info.
---server-read-timeout and --server-write-timeout can be used to control the timeouts on the server. Note that this is the total time for a transfer.
---max-header-bytes controls the maximum number of bytes the server will accept in the HTTP header.
---baseurl controls the URL prefix that rclone serves from. By default rclone will serve from the root. If you used --baseurl "/rclone" then rclone would serve from a URL starting with "/rclone/". This is useful if you wish to proxy rclone serve. Rclone automatically inserts leading and trailing "/" on --baseurl, so --baseurl "rclone", --baseurl "/rclone" and --baseurl "/rclone/" are all treated identically.
---template allows a user to specify a custom markup template for http and webdav serve functions. The server exports the following markup to be used within the template to server pages:
+Use --addr to specify which IP address and port the server should listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all IPs. By default it only listens on localhost. You can use port :0 to let the OS choose an available port.
+If you set --addr to listen on a public or LAN accessible IP address then using Authentication is advised - see the next section for info.
+--server-read-timeout and --server-write-timeout can be used to control the timeouts on the server. Note that this is the total time for a transfer.
+--max-header-bytes controls the maximum number of bytes the server will accept in the HTTP header.
+--baseurl controls the URL prefix that rclone serves from. By default rclone will serve from the root. If you used --baseurl "/rclone" then rclone would serve from a URL starting with "/rclone/". This is useful if you wish to proxy rclone serve. Rclone automatically inserts leading and trailing "/" on --baseurl, so --baseurl "rclone", --baseurl "/rclone" and --baseurl "/rclone/" are all treated identically.
+--template allows a user to specify a custom markup template for HTTP and WebDAV serve functions. The server exports the following markup to be used within the template to server pages:
@@ -3466,23 +3601,23 @@ Server options
Authentication
By default this will serve files without needing a login.
-You can either use an htpasswd file which can take lots of users, or set a single username and password with the --user and --pass flags.
-Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is in standard apache format and supports MD5, SHA1 and BCrypt for basic authentication. Bcrypt is recommended.
+You can either use an htpasswd file which can take lots of users, or set a single username and password with the --user and --pass flags.
+Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is in standard apache format and supports MD5, SHA1 and BCrypt for basic authentication. Bcrypt is recommended.
To create an htpasswd file:
touch htpasswd
htpasswd -B htpasswd user
htpasswd -B htpasswd anotherUser
The password file can be updated while rclone is running.
-Use --realm to set the authentication realm.
+Use --realm to set the authentication realm.
SSL/TLS
-By default this will serve over http. If you want you can serve over https. You will need to supply the --cert and --key flags. If you wish to do client side certificate validation then you will need to supply --client-ca also.
---cert should be either a PEM encoded certificate or a concatenation of that with the CA certificate. --key should be the PEM encoded private key and --client-ca should be the PEM encoded client certificate authority certificate.
+By default this will serve over HTTP. If you want you can serve over HTTPS. You will need to supply the --cert and --key flags. If you wish to do client side certificate validation then you will need to supply --client-ca also.
+--cert should be either a PEM encoded certificate or a concatenation of that with the CA certificate. --key should be the PEM encoded private key and --client-ca should be the PEM encoded client certificate authority certificate.
VFS - Virtual File System
This command uses the VFS layer. This adapts the cloud storage objects that rclone uses into something which looks much more like a disk filing system.
Cloud storage objects have lots of properties which aren't like disk files - you can't extend them or write to the middle of them, so the VFS layer has to deal with that. Because there is no one right way of doing this there are various options explained below.
The VFS layer also implements a directory cache - this caches info about files and directories (but not the data) in memory.
VFS Directory Cache
-Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the mount will appear immediately or invalidate the cache.
+Using the --dir-cache-time flag, you can control how long a directory should be considered up to date and not refreshed from the backend. Changes made through the VFS will appear immediately or invalidate the cache.
--dir-cache-time duration Time to cache directory entries for (default 5m0s)
--poll-interval duration Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable (default 1m0s)
However, changes made directly on the cloud storage by the web interface or a different copy of rclone will only be picked up once the directory cache expires if the backend configured does not support polling for changes. If the backend supports polling, changes will be picked up within the polling interval.
@@ -3545,6 +3680,19 @@ --vfs-cache-mode full
When reading a file rclone will read --buffer-size plus --vfs-read-ahead bytes ahead. The --buffer-size is buffered in memory whereas the --vfs-read-ahead is buffered on disk.
When using this mode it is recommended that --buffer-size is not set too large and --vfs-read-ahead is set large if required.
IMPORTANT not all file systems support sparse files. In particular FAT/exFAT do not. Rclone will perform very badly if the cache directory is on a filesystem which doesn't support sparse files and it will log an ERROR message if one is detected.
+Fingerprinting
+Various parts of the VFS use fingerprinting to see if a local file copy has changed relative to a remote file. Fingerprints are made from:
+
+- size
+- modification time
+- hash
+
+where available on an object.
+On some backends some of these attributes are slow to read (they take an extra API call per object, or extra work per object).
+For example hash is slow with the local and sftp backends as they have to read the entire file and hash it, and modtime is slow with the s3, swift, ftp and qinqstor backends because they need to do an extra API call to fetch it.
+If you use the --vfs-fast-fingerprint flag then rclone will not include the slow operations in the fingerprint. This makes the fingerprinting less accurate but much faster and will improve the opening time of cached files.
+If you are running a vfs cache over local, s3 or swift backends then using this flag is recommended.
+Note that if you change the value of this flag, the fingerprints of the files in the cache may be invalidated and the files will need to be downloaded again.
VFS Chunked Reading
When rclone reads files from a remote it reads them in chunks. This means that rather than requesting the whole file rclone reads the chunk specified. This can reduce the used download quota for some remotes by requesting only chunks from the remote that are actually read, at the cost of an increased number of requests.
These flags control the chunking:
@@ -3559,20 +3707,23 @@
--no-checksum Don't compare checksums on up/download.
--no-modtime Don't read/write the modification time (can speed things up).
--no-seek Don't allow seeking in files.
---read-only Mount read-only.
+--read-only Only allow read-only access.
Sometimes rclone is delivered reads or writes out of order. Rather than seeking rclone will wait a short time for the in sequence read or write to come in. These flags only come into effect when not using an on disk cache file.
--vfs-read-wait duration Time to wait for in-sequence read before seeking (default 20ms)
--vfs-write-wait duration Time to wait for in-sequence write before giving error (default 1s)
-When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from cache (the related global flag --checkers have no effect on mount).
+When using VFS write caching (--vfs-cache-mode with value writes or full), the global flag --transfers can be set to adjust the number of parallel uploads of modified files from the cache (the related global flag --checkers has no effect on the VFS).
--transfers int Number of file transfers to run in parallel (default 4)
VFS Case Sensitivity
Linux file systems are case-sensitive: two files can differ only by case, and the exact case must be used when opening a file.
File systems in modern Windows are case-insensitive but case-preserving: although existing files can be opened using any case, the exact case used to create the file is preserved and available for programs to query. It is not allowed for two files in the same directory to differ only by case.
Usually file systems on macOS are case-insensitive. It is possible to make macOS file systems case-sensitive but that is not the default.
-The --vfs-case-insensitive mount flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the mounted file system as-is. If the flag is "true" (or appears without a value on command line), rclone may perform a "fixup" as explained below.
-The user may specify a file name to open/delete/rename/etc with a case different than what is stored on mounted file system. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by an underlying mounted file system.
-Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system mounted by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
+The --vfs-case-insensitive VFS flag controls how rclone handles these two cases. If its value is "false", rclone passes file names to the remote as-is. If the flag is "true" (or appears without a value on the command line), rclone may perform a "fixup" as explained below.
+The user may specify a file name to open/delete/rename/etc with a case different than what is stored on the remote. If an argument refers to an existing file with exactly the same name, then the case of the existing file on the disk will be used. However, if a file name with exactly the same name is not found but a name differing only by case exists, rclone will transparently fixup the name. This fixup happens only when an existing file is requested. Case sensitivity of file names created anew by rclone is controlled by the underlying remote.
+Note that case sensitivity of the operating system running rclone (the target) may differ from case sensitivity of a file system presented by rclone (the source). The flag controls whether "fixup" is performed to satisfy the target.
If the flag is not provided on the command line, then its default value depends on the operating system where rclone runs: "true" on Windows and macOS, "false" otherwise. If the flag is provided without a value, then it is "true".
+VFS Disk Options
+This flag allows you to manually set the statistics about the filing system. It can be useful when those statistics cannot be read correctly automatically.
+--vfs-disk-space-total-size Manually set the total disk space size (example: 256G, default: -1)
Alternate report of used bytes
Some backends, most notably S3, do not report the amount of bytes used. If you need this information to be available when running df on the filesystem, then pass the flag --vfs-used-is-size to rclone. With this flag set, instead of relying on the backend to report this information, rclone will scan the whole remote similar to rclone size and compute the total used space itself.
WARNING. Contrary to rclone size, this flag ignores filters so that the result is accurate. However, this is very inefficient and may cost lots of API calls resulting in extra charges. Use it as a last resort and only with caching.
@@ -3628,7 +3779,7 @@ Options
--no-seek Don't allow seeking in files
--pass string Password for authentication
--poll-interval duration Time to wait between polling for changes, must be smaller than dir-cache-time and only on supported remotes (set 0 to disable) (default 1m0s)
- --read-only Mount read-only
+ --read-only Only allow read-only access
--realm string Realm for authentication (default "rclone")
--server-read-timeout duration Timeout for server reading data (default 1h0m0s)
--server-write-timeout duration Timeout for server writing data (default 1h0m0s)
@@ -3641,6 +3792,8 @@ Options
--vfs-cache-mode CacheMode Cache mode off|minimal|writes|full (default off)
--vfs-cache-poll-interval duration Interval to poll the cache for stale objects (default 1m0s)
--vfs-case-insensitive If a file name not found, find a case insensitive match
+ --vfs-disk-space-total-size SizeSuffix Specify the total space of disk (default off)
+ --vfs-fast-fingerprint Use fast (less accurate) fingerprints for change detection
--vfs-read-ahead SizeSuffix Extra read ahead over --buffer-size when using cache-mode full
--vfs-read-chunk-size SizeSuffix Read the source objects in chunks (default 128Mi)
--vfs-read-chunk-size-limit SizeSuffix If greater than --vfs-read-chunk-size, double the chunk size after each chunk read, until the limit is reached ('off' is unlimited) (default off)
@@ -3655,7 +3808,7 @@ SEE ALSO
rclone settier
Changes storage class/tier of objects in remote.
-Synopsis
+Synopsis
rclone settier changes storage tier or class at remote if supported. Few cloud storage services provides different storage classes on objects, for example AWS S3 and Glacier, Azure Blob storage - Hot, Cool and Archive, Google Cloud Storage, Regional Storage, Nearline, Coldline etc.
Note that, certain tier changes make objects not available to access immediately. For example tiering to archive in azure blob storage makes objects in frozen state, user can restore by setting tier to Hot/Cool, similarly S3 to Glacier makes object inaccessible.true
You can use it to tier single object
@@ -3674,7 +3827,7 @@ SEE ALSO
rclone test
Run a test command
-Synopsis
+Synopsis
Rclone test is used to run test commands.
Select which test comand you want with the subcommand, eg
rclone test memory remote:
@@ -3689,6 +3842,7 @@ SEE ALSO
rclone test changenotify - Log any change notify requests for the remote passed in.
rclone test histogram - Makes a histogram of file name characters.
rclone test info - Discovers file name or other limitations for paths.
+rclone test makefile - Make files with random contents of the size given
rclone test makefiles - Make a random file hierarchy in a directory
rclone test memory - Load all the objects at remote:path into memory and report memory stats.
@@ -3705,7 +3859,7 @@ SEE ALSO
rclone test histogram
Makes a histogram of file name characters.
-Synopsis
+Synopsis
This command outputs JSON which shows the histogram of characters used in filenames in the remote:path specified.
The data doesn't contain any identifying information but is useful for the rclone developers when developing filename compression.
rclone test histogram [remote:path] [flags]
@@ -3718,7 +3872,7 @@ SEE ALSO
rclone test info
Discovers file name or other limitations for paths.
-Synopsis
+Synopsis
rclone info discovers what filenames and upload methods are possible to write to the paths passed in and how long they can be. It can take some time. It will write test files into the remote:path passed in. It outputs a bit of go code for each one.
NB this can create undeletable files and other hazards - use with care
rclone test info [remote:path]+ [flags]
@@ -3736,36 +3890,57 @@ SEE ALSO
+rclone test makefile
+Make files with random contents of the size given
+rclone test makefile <size> [<file>]+ [flags]
+Options
+ --ascii Fill files with random ASCII printable bytes only
+ --chargen Fill files with a ASCII chargen pattern
+ -h, --help help for makefile
+ --pattern Fill files with a periodic pattern
+ --seed int Seed for the random number generator (0 for random) (default 1)
+ --sparse Make the files sparse (appear to be filled with ASCII 0x00)
+ --zero Fill files with ASCII 0x00
+See the global flags page for global options not listed here.
+SEE ALSO
+
rclone test makefiles
Make a random file hierarchy in a directory
rclone test makefiles <dir> [flags]
-Options
- --files int Number of files to create (default 1000)
+Options
+ --ascii Fill files with random ASCII printable bytes only
+ --chargen Fill files with a ASCII chargen pattern
+ --files int Number of files to create (default 1000)
--files-per-directory int Average number of files per directory (default 10)
-h, --help help for makefiles
--max-file-size SizeSuffix Maximum size of files to create (default 100)
--max-name-length int Maximum size of file names (default 12)
--min-file-size SizeSuffix Minimum size of file to create
--min-name-length int Minimum size of file names (default 4)
- --seed int Seed for the random number generator (0 for random) (default 1)
+ --pattern Fill files with a periodic pattern
+ --seed int Seed for the random number generator (0 for random) (default 1)
+ --sparse Make the files sparse (appear to be filled with ASCII 0x00)
+ --zero Fill files with ASCII 0x00
See the global flags page for global options not listed here.
-SEE ALSO
+SEE ALSO
rclone test memory
Load all the objects at remote:path into memory and report memory stats.
rclone test memory remote:path [flags]
-Options
+Options
-h, --help help for memory
See the global flags page for global options not listed here.
-SEE ALSO
+SEE ALSO
rclone touch
Create new file or change file modification time.
-Synopsis
+Synopsis
Set the modification time on file(s) as specified by remote:path to have the current time.
If remote:path does not exist then a zero sized file will be created, unless --no-create or --recursive is provided.
If --recursive is used then recursively sets the modification time on all existing files that is found under the path. Filters are supported, and you can test with the --dry-run or the --interactive flag.
@@ -3777,20 +3952,20 @@ Synopsis
Note that value of --timestamp is in UTC. If you want local time then add the --localtime flag.
rclone touch remote:path [flags]
-Options
+Options
-h, --help help for touch
--localtime Use localtime for timestamp, not UTC
-C, --no-create Do not create the file if it does not exist (implied with --recursive)
-R, --recursive Recursively touch all files
-t, --timestamp string Use specified time instead of the current time of day
See the global flags page for global options not listed here.
-SEE ALSO
+SEE ALSO
- rclone - Show help for rclone commands, flags and backends.
rclone tree
List the contents of the remote in a tree like fashion.
-Synopsis
+Synopsis
rclone tree lists the contents of a remote in a similar way to the unix tree command.
For example
$ rclone tree remote:path
@@ -3803,10 +3978,11 @@ Synopsis
└── file5
1 directories, 5 files
-You can use any of the filtering options with the tree command (e.g. --include and --exclude). You can also use --fast-list.
-The tree command has many options for controlling the listing which are compatible with the tree command. Note that not all of them have short options as they conflict with rclone's short options.
+You can use any of the filtering options with the tree command (e.g. --include and --exclude. You can also use --fast-list.
+The tree command has many options for controlling the listing which are compatible with the tree command, for example you can include file sizes with --size. Note that not all of them have short options as they conflict with rclone's short options.
+For a more interactive navigation of the remote see the ncdu command.
rclone tree remote:path [flags]
-Options
+Options
-a, --all All files are listed (list . files too)
-C, --color Turn colorization on always
-d, --dirs-only List directories only
@@ -3828,7 +4004,7 @@ Options
-U, --unsorted Leave files unsorted
--version Sort files alphanumerically by version
See the global flags page for global options not listed here.
-SEE ALSO
+SEE ALSO
- rclone - Show help for rclone commands, flags and backends.
@@ -3940,7 +4116,119 @@ Server Side Copy
This can be used when scripting to make aged backups efficiently, e.g.
rclone sync -i remote:current-backup remote:previous-backup
rclone sync -i /path/to/files remote:current-backup
-Options
+
+Metadata is data about a file which isn't the contents of the file. Normally rclone only preserves the modification time and the content (MIME) type where possible.
+Rclone supports preserving all the available metadata on files (not directories) when using the --metadata or -M flag.
+Exactly what metadata is supported and what that support means depends on the backend. Backends that support metadata have a metadata section in their docs and are listed in the features table (Eg local, s3)
+Rclone only supports a one-time sync of metadata. This means that metadata will be synced from the source object to the destination object only when the source object has changed and needs to be re-uploaded. If the metadata subsequently changes on the source object without changing the object itself then it won't be synced to the destination object. This is in line with the way rclone syncs Content-Type without the --metadata flag.
+Using --metadata when syncing from local to local will preserve file attributes such as file mode, owner, extended attributes (not Windows).
+Note that arbitrary metadata may be added to objects using the --metadata-set key=value flag when the object is first uploaded. This flag can be repeated as many times as necessary.
+
+Metadata is divided into two type. System metadata and User metadata.
+Metadata which the backend uses itself is called system metadata. For example on the local backend the system metadata uid will store the user ID of the file when used on a unix based platform.
+Arbitrary metadata is called user metadata and this can be set however is desired.
+When objects are copied from backend to backend, they will attempt to interpret system metadata if it is supplied. Metadata may change from being user metadata to system metadata as objects are copied between different backends. For example copying an object from s3 sets the content-type metadata. In a backend which understands this (like azureblob) this will become the Content-Type of the object. In a backend which doesn't understand this (like the local backend) this will become user metadata. However should the local object be copied back to s3, the Content-Type will be set correctly.
+
+Rclone implements a metadata framework which can read metadata from an object and write it to the object when (and only when) it is being uploaded.
+This metadata is stored as a dictionary with string keys and string values.
+There are some limits on the names of the keys (these may be clarified further in the future).
+
+- must be lower case
+- may be
a-z 0-9 containing . - or _
+- length is backend dependent
+
+Each backend can provide system metadata that it understands. Some backends can also store arbitrary user metadata.
+Where possible the key names are standardized, so, for example, it is possible to copy object metadata from s3 to azureblob for example and metadata will be translated apropriately.
+Some backends have limits on the size of the metadata and rclone will give errors on upload if they are exceeded.
+
+The goal of the implementation is to
+
+- Preserve metadata if at all possible
+- Interpret metadata if at all possible
+
+The consequences of 1 is that you can copy an S3 object to a local disk then back to S3 losslessly. Likewise you can copy a local file with file attributes and xattrs from local disk to s3 and back again losslessly.
+The consequence of 2 is that you can copy an S3 object with metadata to Azureblob (say) and have the metadata appear on the Azureblob object also.
+
+Here is a table of standard system metadata which, if appropriate, a backend may implement.
+
+
+
+
+
+
+
+
+
+
+
+| mode |
+File type and mode: octal, unix style |
+0100664 |
+
+
+| uid |
+User ID of owner: decimal number |
+500 |
+
+
+| gid |
+Group ID of owner: decimal number |
+500 |
+
+
+| rdev |
+Device ID (if special file) => hexadecimal |
+0 |
+
+
+| atime |
+Time of last access: RFC 3339 |
+2006-01-02T15:04:05.999999999Z07:00 |
+
+
+| mtime |
+Time of last modification: RFC 3339 |
+2006-01-02T15:04:05.999999999Z07:00 |
+
+
+| btime |
+Time of file creation (birth): RFC 3339 |
+2006-01-02T15:04:05.999999999Z07:00 |
+
+
+| cache-control |
+Cache-Control header |
+no-cache |
+
+
+| content-disposition |
+Content-Disposition header |
+inline |
+
+
+| content-encoding |
+Content-Encoding header |
+gzip |
+
+
+| content-language |
+Content-Language header |
+en-US |
+
+
+| content-type |
+Content-Type header |
+text/plain |
+
+
+
+The metadata keys mtime and content-type will take precedence if supplied in the metadata over reading the Content-Type or modification time of the source object.
+Hashes are not included in system metadata as there is a well defined way of reading those already.
+Options
Rclone has a number of options to control its behaviour.
Options that take parameters can have the values passed in two ways, --option=value or --option value. However boolean (true/false) options behave slightly differently to the other options in that --boolean sets the option to true and the absence of the flag sets it to false. It is also possible to specify --boolean=false or --boolean=true. Note that --boolean false is not valid - this is parsed as --boolean and the false is parsed as an extra command line argument for rclone.
Options which use TIME use the go time parser. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
@@ -4010,8 +4298,9 @@ --check-first
It can also be useful to ensure perfect ordering when using --order-by.
Using this flag can use more memory as it effectively sets --max-backlog to infinite. This means that all the info on the objects to transfer is held in memory before the transfers start.
--checkers=N
-The number of checkers to run in parallel. Checkers do the equality checking of files during a sync. For some storage systems (e.g. S3, Swift, Dropbox) this can take a significant amount of time so they are run in parallel.
-The default is to run 8 checkers in parallel.
+Originally controlling just the number of file checkers to run in parallel, e.g. by rclone copy. Now a fairly universal parallelism control used by rclone in several places.
+Note: checkers do the equality checking of files during a sync. For some storage systems (e.g. S3, Swift, Dropbox) this can take a significant amount of time so they are run in parallel.
+The default is to run 8 checkers in parallel. However, in case of slow-reacting backends you may need to lower (rather than increase) this default by setting --checkers to 4 or less threads. This is especially advised if you are experiencing backend server crashes during file checking phase (e.g. on subsequent or top-up backups where little or no file copying is done and checking takes up most of the time). Increase this setting only with utmost care, while monitoring your server health and file checking throughput.
-c, --checksum
Normally rclone will look at modification time and size of files to see if they are equal. If you set this flag then rclone will check the file hash and size to determine if files are equal.
This is useful when the remote doesn't support setting modified time and a more accurate sync is desired than just checking the file size.
@@ -4068,7 +4357,8 @@ --copy-dest=DIR
The remote in use must support server-side copy and you must use the same remote as the destination of the sync. The compare directory must not overlap the destination directory.
See --compare-dest and --backup-dir.
--dedupe-mode MODE
-Mode to run dedupe command in. One of interactive, skip, first, newest, oldest, rename. The default is interactive. See the dedupe command for more information as to what these options mean.
+Mode to run dedupe command in. One of interactive, skip, first, newest, oldest, rename. The default is interactive.
+See the dedupe command for more information as to what these options mean.
--disable FEATURE,FEATURE,...
This disables a comma separated list of optional features. For example to disable server-side move and server-side copy use:
--disable move,copy
@@ -4118,11 +4408,11 @@
--human-readable
Rclone commands output values for sizes (e.g. number of bytes) and counts (e.g. number of files) either as raw numbers, or in human-readable format.
In human-readable format the values are scaled to larger units, indicated with a suffix shown after the value, and rounded to three decimals. Rclone consistently uses binary units (powers of 2) for sizes and decimal units (powers of 10) for counts. The unit prefix for size is according to IEC standard notation, e.g. Ki for kibi. Used with byte unit, 1 KiB means 1024 Byte. In list type of output, only the unit prefix appended to the value (e.g. 9.762Ki), while in more textual output the full unit is shown (e.g. 9.762 KiB). For counts the SI standard notation is used, e.g. prefix k for kilo. Used with file counts, 1k means 1000 files.
-The various list commands output raw numbers by default. Option --human-readable will make them output values in human-readable format instead (with the short unit prefix).
-The about command outputs human-readable by default, with a command-specific option --full to output the raw numbers instead.
-Command size outputs both human-readable and raw numbers in the same output.
-The tree command also considers --human-readable, but it will not use the exact same notation as the other commands: It rounds to one decimal, and uses single letter suffix, e.g. K instead of Ki. The reason for this is that it relies on an external library.
-The interactive command ncdu shows human-readable by default, and responds to key u for toggling human-readable format.
+The various list commands output raw numbers by default. Option --human-readable will make them output values in human-readable format instead (with the short unit prefix).
+The about command outputs human-readable by default, with a command-specific option --full to output the raw numbers instead.
+Command size outputs both human-readable and raw numbers in the same output.
+The tree command also considers --human-readable, but it will not use the exact same notation as the other commands: It rounds to one decimal, and uses single letter suffix, e.g. K instead of Ki. The reason for this is that it relies on an external library.
+The interactive command ncdu shows human-readable by default, and responds to key u for toggling human-readable format.
--ignore-case-sync
Using this option will cause rclone to ignore the case of the files when synchronizing so files will not be copied/synced when the existing filenames are the same, even if the casing is different.
--ignore-checksum
@@ -4208,6 +4498,10 @@ --max-transfer=SIZE
Rclone will stop transferring when it has reached the size specified. Defaults to off.
When the limit is reached all transfers will stop immediately.
Rclone will exit with exit code 8 if the transfer limit is reached.
+
+Setting this flag enables rclone to copy the metadata from the source to the destination. For local backends this is ownership, permissions, xattr etc. See the #metadata for more info.
+
+Add metadata key = value when uploading. This can be repeated as many times as required. See the #metadata for more info.
--cutoff-mode=hard|soft|cautious
This modifies the behavior of --max-transfer Defaults to --cutoff-mode=hard.
Specifying --cutoff-mode=hard will stop transferring immediately when Rclone reaches the limit.
@@ -4434,6 +4728,7 @@ --timeout=TIME
--transfers=N
The number of file transfers to run in parallel. It can sometimes be useful to set this to a smaller number if the remote is giving a lot of timeouts or bigger if you have lots of bandwidth and a fast remote.
The default is to run 4 file transfers in parallel.
+Look at --multi-thread-streams if you would like to control single file transfers.
-u, --update
This forces rclone to skip any files which exist on the destination and have a modified time that is newer than the source file.
This can be useful in avoiding needless transfers when transferring to a remote which doesn't support modification times directly (or when using --use-server-modtime to avoid extra API calls) as it is more accurate than a --size-only check and faster than using --checksum. On such remotes (or when using --use-server-modtime) the time checked will be the uploaded time.
@@ -4452,6 +4747,7 @@ --use-server-modtime
-v, -vv, --verbose
With -v rclone will tell you about each file that is transferred and a small number of significant events.
With -vv rclone will become very verbose telling you about every file it considers and transfers. Please send bug reports with a log with this setting.
+When setting verbosity as an environment variable, use RCLONE_VERBOSE=1 or RCLONE_VERBOSE=2 for -v and -vv respectively.
-V, --version
Prints the version number
SSL/TLS options
@@ -4552,6 +4848,7 @@ Filtering
--filter-from
--exclude
--exclude-from
+--exclude-if-present
--include
--include-from
--files-from
@@ -4600,11 +4897,12 @@ List of exit codes
Environment Variables
Rclone can be configured entirely using environment variables. These can be used to set defaults for options or config file entries.
-Options
+Options
Every option in rclone can have its default set by environment variable.
To find the name of the environment variable, first, take the long option name, strip the leading --, change - to _, make upper case and prepend RCLONE_.
For example, to always set --stats 5s, set the environment variable RCLONE_STATS=5s. If you set stats on the command line this will override the environment variable setting.
Or to always use the trash in drive --drive-use-trash, set RCLONE_DRIVE_USE_TRASH=true.
+Verbosity is slightly different, the environment variable equivalent of --verbose or -v is RCLONE_VERBOSE=1, or for -vv, RCLONE_VERBOSE=2.
The same parser is used for the options and the environment variables so they take exactly the same form.
The options set by environment variables can be seen with the -vv flag, e.g. rclone version -vv.
Config file
@@ -4714,6 +5012,19 @@ Configuring by copying the confi
Configuration file is stored at:
/home/user/.rclone.conf
Now transfer it to the remote box (scp, cut paste, ftp, sftp, etc.) and place it in the correct place (use rclone config file on the remote box to find out where).
+Configuring using SSH Tunnel
+Linux and MacOS users can utilize SSH Tunnel to redirect the headless box port 53682 to local machine by using the following command:
+ssh -L localhost:53682:localhost:53682 username@remote_server
+Then on the headless box run rclone config and answer Y to the Use auto config? question.
+...
+Remote config
+Use auto config?
+ * Say Y if not sure
+ * Say N if you are working on a remote or headless machine
+y) Yes (default)
+n) No
+y/n> y
+Then copy and paste the auth url http://127.0.0.1:53682/auth?state=xxxxxxxxxxxx to the browser on your local machine, complete the auth and it is done.
Filtering, includes and excludes
Filter flags determine which files rclone sync, move, ls, lsl, md5sum, sha1sum, size, delete, check and similar commands apply to.
They are specified in terms of path/file name patterns; path/file lists; file age and size, or presence of a file in a directory. Bucket based remotes without the concept of directory apply filters to object key, age and size in an analogous way.
@@ -4780,7 +5091,7 @@ Pattern syntax
- matches "POTATO"
Using regular expressions in filter patterns
The syntax of filter patterns is glob style matching (like bash uses) to make things easy for users. However this does not provide absolute control over the matching, so for advanced users rclone also provides a regular expression syntax.
-The regular expressions used are as defined in the Go regular expression reference. Regular expressions should be enclosed in {{ }}. They will match only the last path segment if the glob doesn't start with / or the whole path name if it does.
+The regular expressions used are as defined in the Go regular expression reference. Regular expressions should be enclosed in {{ }}. They will match only the last path segment if the glob doesn't start with / or the whole path name if it does. Note that rclone does not attempt to parse the supplied regular expression, meaning that using any regular expression filter will prevent rclone from using directory filter rules, as it will instead check every path against the supplied regular expression(s).
Here is how the {{regexp}} is transformed into an full regular expression to match the entire path:
{{regexp}} becomes (^|/)(regexp)$
/{{regexp}} becomes ^(regexp)$
@@ -4949,14 +5260,15 @@ How filter rules are applied to f
Any path/file included at that stage is processed by the rclone command.
--files-from and --files-from-raw flags over-ride and cannot be combined with other filter options.
To see the internal combined rule list, in regular expression form, for a command add the --dump filters flag. Running an rclone command with --dump filters and -vv flags lists the internal filter elements and shows how they are applied to each source path/file. There is not currently a means provided to pass regular expression filter options into rclone directly though character class filter rules contain character classes. Go regular expression reference
-How filter rules are applied to directories
+How filter rules are applied to directories
Rclone commands are applied to path/file names not directories. The entire contents of a directory can be matched to a filter by the pattern directory/* or recursively by directory/**.
Directory filter rules are defined with a closing / separator.
E.g. /directory/subdirectory/ is an rclone directory filter rule.
Rclone commands can use directory filter rules to determine whether they recurse into subdirectories. This potentially optimises access to a remote by avoiding listing unnecessary directories. Whether optimisation is desirable depends on the specific filter rules and source remote content.
+If any regular expression filters are in use, then no directory recursion optimisation is possible, as rclone must check every path against the supplied regular expression(s).
Directory recursion optimisation occurs if either:
-A source remote does not support the rclone ListR primitive. local, sftp, Microsoft OneDrive and WebDav do not support ListR. Google Drive and most bucket type storage do. Full list
+A source remote does not support the rclone ListR primitive. local, sftp, Microsoft OneDrive and WebDAV do not support ListR. Google Drive and most bucket type storage do. Full list
On other remotes (those that support ListR), if the rclone command is not naturally recursive, and provided it is not run with the --fast-list flag. ls, lsf -R and size are naturally recursive but sync, copy and move are not.
Whenever the --disable ListR flag is applied to an rclone command.
@@ -5157,7 +5469,7 @@ --dump filtersDumps the defined filters to standard output in regular expression format.
Useful for debugging.
Exclude directory based on a file
-The --exclude-if-present flag controls whether a directory is within the scope of an rclone command based on the presence of a named file within it.
+The --exclude-if-present flag controls whether a directory is within the scope of an rclone command based on the presence of a named file within it. The flag can be repeated to check for multiple file names, presence of any of them will exclude the directory.
This flag has a priority over other filter flags.
E.g. for the following directory structure:
dir1/file1
@@ -5165,7 +5477,6 @@ Exclude directory based on a file
The command rclone ls --exclude-if-present .ignore dir1 does not list dir3, file3 or .ignore.
---exclude-if-present can only be used once in an rclone command.
Common pitfalls
The most frequent filter support issues on the rclone forum are:
@@ -5245,8 +5556,8 @@ Project
If you have questions then please ask them on the rclone forum.
Remote controlling rclone with its API
If rclone is run with the --rc flag then it starts an HTTP server which can be used to remote control rclone using its API.
-You can either use the rclone rc command to access the API or use HTTP directly.
-If you just want to run a remote control then see the rcd command.
+You can either use the rc command to access the API or use HTTP directly.
+If you just want to run a remote control then see the rcd command.
Supported parameters
--rc
Flag to start the http server listen on remote requests
@@ -5310,6 +5621,11 @@ --rc-no-auth
By default rclone will require authorisation to have been set up on the rc interface in order to use any methods which access any rclone remotes. Eg operations/list is denied as it involved creating a remote as is sync/copy.
If this is set then no authorisation will be required on the server to use these methods. The alternative is to use --rc-user and --rc-pass and use these credentials in the request.
Default Off.
+--rc-baseurl
+Prefix for URLs.
+Default is root
+--rc-template
+User-specified template.
Accessing the remote control via the rclone rc command
Rclone itself implements the remote control protocol in its rclone rc command.
You can use it like this
@@ -5516,30 +5832,30 @@ config/create: create the config for a remote.
- result - result to restart with - used with continue
-See the config create command command for more information on the above.
+See the config create command for more information on the above.
Authentication is required for this call.
config/delete: Delete a remote in the config file.
Parameters:
- name - name of remote to delete
-See the config delete command command for more information on the above.
+See the config delete command for more information on the above.
Authentication is required for this call.
config/dump: Dumps the config file.
Returns a JSON object: - key: value
Where keys are remote names and values are the config parameters.
-See the config dump command command for more information on the above.
+See the config dump command for more information on the above.
Authentication is required for this call.
config/get: Get a remote in the config file.
Parameters:
- name - name of remote to get
-See the config dump command command for more information on the above.
+See the config dump command for more information on the above.
Authentication is required for this call.
config/listremotes: Lists the remotes in the config file.
Returns - remotes - array of remote names
-See the listremotes command command for more information on the above.
+See the listremotes command for more information on the above.
Authentication is required for this call.
config/password: password the config for a remote.
This takes the following parameters:
@@ -5547,11 +5863,11 @@ config/password: password the config for a remote.
name - name of remote
parameters - a map of { "key": "value" } pairs
-See the config password command command for more information on the above.
+See the config password command for more information on the above.
Authentication is required for this call.
config/providers: Shows how providers are configured in the config file.
Returns a JSON object: - providers - array of objects
-See the config providers command command for more information on the above.
+See the config providers command for more information on the above.
Authentication is required for this call.
config/update: update the config for a remote.
This takes the following parameters:
@@ -5569,7 +5885,7 @@ config/update: update the config for a remote.
result - result to restart with - used with continue
-See the config update command command for more information on the above.
+See the config update command for more information on the above.
Authentication is required for this call.
core/bwlimit: Set the bandwidth limit.
This sets the bandwidth limit to the string passed in. This should be a single bandwidth limit entry or a pair of upload:download bandwidth.
@@ -5882,14 +6198,14 @@ operations/about: Return the space used on the remote<
fs - a remote name string e.g. "drive:"
The result is as returned from rclone about --json
-See the about command command for more information on the above.
+See the about command for more information on the above.
Authentication is required for this call.
operations/cleanup: Remove trashed files in the remote or path
This takes the following parameters:
- fs - a remote name string e.g. "drive:"
-See the cleanup command command for more information on the above.
+See the cleanup command for more information on the above.
Authentication is required for this call.
operations/copyfile: Copy a file from source remote to destination remote
This takes the following parameters:
@@ -5906,15 +6222,16 @@ operations/copyurl: Copy the URL to the object
fs - a remote name string e.g. "drive:"
remote - a path within that remote e.g. "dir"
url - string, URL to read from
-autoFilename - boolean, set to true to retrieve destination file name from url See the copyurl command command for more information on the above.
+autoFilename - boolean, set to true to retrieve destination file name from url
+See the copyurl command for more information on the above.
Authentication is required for this call.
operations/delete: Remove files in the path
This takes the following parameters:
- fs - a remote name string e.g. "drive:"
-See the delete command command for more information on the above.
+See the delete command for more information on the above.
Authentication is required for this call.
operations/deletefile: Remove the single file pointed to
This takes the following parameters:
@@ -5922,7 +6239,7 @@ operations/deletefile: Remove the single file poi
fs - a remote name string e.g. "drive:"
remote - a path within that remote e.g. "dir"
-See the deletefile command command for more information on the above.
+See the deletefile command for more information on the above.
Authentication is required for this call.
operations/fsinfo: Return information about the remote
This takes the following parameters:
@@ -5931,46 +6248,103 @@ operations/fsinfo: Return information about the remot
This returns info about the remote passed in;
{
- // optional features and whether they are available or not
- "Features": {
- "About": true,
- "BucketBased": false,
- "CanHaveEmptyDirectories": true,
- "CaseInsensitive": false,
- "ChangeNotify": false,
- "CleanUp": false,
- "Copy": false,
- "DirCacheFlush": false,
- "DirMove": true,
- "DuplicateFiles": false,
- "GetTier": false,
- "ListR": false,
- "MergeDirs": false,
- "Move": true,
- "OpenWriterAt": true,
- "PublicLink": false,
- "Purge": true,
- "PutStream": true,
- "PutUnchecked": false,
- "ReadMimeType": false,
- "ServerSideAcrossConfigs": false,
- "SetTier": false,
- "SetWrapper": false,
- "UnWrap": false,
- "WrapFs": false,
- "WriteMimeType": false
- },
- // Names of hashes available
- "Hashes": [
- "MD5",
- "SHA-1",
- "DropboxHash",
- "QuickXorHash"
- ],
- "Name": "local", // Name as created
- "Precision": 1, // Precision of timestamps in ns
- "Root": "/", // Path as created
- "String": "Local file system at /" // how the remote will appear in logs
+ // optional features and whether they are available or not
+ "Features": {
+ "About": true,
+ "BucketBased": false,
+ "BucketBasedRootOK": false,
+ "CanHaveEmptyDirectories": true,
+ "CaseInsensitive": false,
+ "ChangeNotify": false,
+ "CleanUp": false,
+ "Command": true,
+ "Copy": false,
+ "DirCacheFlush": false,
+ "DirMove": true,
+ "Disconnect": false,
+ "DuplicateFiles": false,
+ "GetTier": false,
+ "IsLocal": true,
+ "ListR": false,
+ "MergeDirs": false,
+ "MetadataInfo": true,
+ "Move": true,
+ "OpenWriterAt": true,
+ "PublicLink": false,
+ "Purge": true,
+ "PutStream": true,
+ "PutUnchecked": false,
+ "ReadMetadata": true,
+ "ReadMimeType": false,
+ "ServerSideAcrossConfigs": false,
+ "SetTier": false,
+ "SetWrapper": false,
+ "Shutdown": false,
+ "SlowHash": true,
+ "SlowModTime": false,
+ "UnWrap": false,
+ "UserInfo": false,
+ "UserMetadata": true,
+ "WrapFs": false,
+ "WriteMetadata": true,
+ "WriteMimeType": false
+ },
+ // Names of hashes available
+ "Hashes": [
+ "md5",
+ "sha1",
+ "whirlpool",
+ "crc32",
+ "sha256",
+ "dropbox",
+ "mailru",
+ "quickxor"
+ ],
+ "Name": "local", // Name as created
+ "Precision": 1, // Precision of timestamps in ns
+ "Root": "/", // Path as created
+ "String": "Local file system at /", // how the remote will appear in logs
+ // Information about the system metadata for this backend
+ "MetadataInfo": {
+ "System": {
+ "atime": {
+ "Help": "Time of last access",
+ "Type": "RFC 3339",
+ "Example": "2006-01-02T15:04:05.999999999Z07:00"
+ },
+ "btime": {
+ "Help": "Time of file birth (creation)",
+ "Type": "RFC 3339",
+ "Example": "2006-01-02T15:04:05.999999999Z07:00"
+ },
+ "gid": {
+ "Help": "Group ID of owner",
+ "Type": "decimal number",
+ "Example": "500"
+ },
+ "mode": {
+ "Help": "File type and mode",
+ "Type": "octal, unix style",
+ "Example": "0100664"
+ },
+ "mtime": {
+ "Help": "Time of last modification",
+ "Type": "RFC 3339",
+ "Example": "2006-01-02T15:04:05.999999999Z07:00"
+ },
+ "rdev": {
+ "Help": "Device ID (if special file)",
+ "Type": "hexadecimal",
+ "Example": "1abc"
+ },
+ "uid": {
+ "Help": "User ID of owner",
+ "Type": "decimal number",
+ "Example": "500"
+ }
+ },
+ "Help": "Textual help string\n"
+ }
}
This command does not have a command line equivalent so use this instead:
rclone rc --loopback operations/fsinfo fs=remote:
@@ -5989,6 +6363,7 @@ operations/list: List the given remote and path in JSON
noMimeType - If set don't show mime types
dirsOnly - If set only show directories
filesOnly - If set only show files
+metadata - If set return metadata of objects also
hashTypes - array of strings of hash types to show if showHash set
@@ -5999,7 +6374,7 @@ operations/list: List the given remote and path in JSON
This is an array of objects as described in the lsjson command
-See the lsjson command for more information on the above and examples.
+See the lsjson command for more information on the above and examples.
Authentication is required for this call.
operations/mkdir: Make a destination directory or container
This takes the following parameters:
@@ -6007,7 +6382,7 @@ operations/mkdir: Make a destination directory or cont
fs - a remote name string e.g. "drive:"
remote - a path within that remote e.g. "dir"
-See the mkdir command command for more information on the above.
+See the mkdir command for more information on the above.
Authentication is required for this call.
operations/movefile: Move a file from source remote to destination remote
This takes the following parameters:
@@ -6030,7 +6405,7 @@ operations/publiclink: Create or retrieve a publi
- url - URL of the resource
-
See the link command command for more information on the above.
+See the link command for more information on the above.
Authentication is required for this call.
operations/purge: Remove a directory or container and all of its contents
This takes the following parameters:
@@ -6038,7 +6413,7 @@ operations/purge: Remove a directory or container and
fs - a remote name string e.g. "drive:"
remote - a path within that remote e.g. "dir"
-See the purge command command for more information on the above.
+See the purge command for more information on the above.
Authentication is required for this call.
operations/rmdir: Remove an empty directory or container
This takes the following parameters:
@@ -6046,15 +6421,16 @@ operations/rmdir: Remove an empty directory or contain
fs - a remote name string e.g. "drive:"
remote - a path within that remote e.g. "dir"
-See the rmdir command command for more information on the above.
+See the rmdir command for more information on the above.
Authentication is required for this call.
operations/rmdirs: Remove all the empty directories in the path
This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
-- leaveRoot - boolean, set to true not to delete the root See the rmdirs command command for more information on the above.
+- leaveRoot - boolean, set to true not to delete the root
+See the rmdirs command for more information on the above.
Authentication is required for this call.
operations/size: Count the number of bytes and files in remote
This takes the following parameters:
@@ -6066,7 +6442,7 @@ operations/size: Count the number of bytes and files in
count - number of files
bytes - number of bytes in those files
-See the size command command for more information on the above.
+See the size command for more information on the above.
Authentication is required for this call.
operations/stat: Give information about the supplied file or directory
This takes the following parameters
@@ -6083,15 +6459,16 @@ operations/stat: Give information about the supplied fi
item - an object as described in the lsjson command. Will be null if not found.
Note that if you are only interested in files then it is much more efficient to set the filesOnly flag in the options.
-See the lsjson command for more information on the above and examples.
+See the lsjson command for more information on the above and examples.
Authentication is required for this call.
operations/uploadfile: Upload file using multiform/form-data
This takes the following parameters:
- fs - a remote name string e.g. "drive:"
- remote - a path within that remote e.g. "dir"
-- each part in body represents a file to be uploaded See the uploadfile command command for more information on the above.
+- each part in body represents a file to be uploaded
+See the uploadfile command for more information on the above.
Authentication is required for this call.
options/blocks: List all the option blocks
Returns: - options - a list of the options block names
@@ -6218,7 +6595,7 @@ sync/copy: copy a directory from source remote to destination
dstFs - a remote name string e.g. "drive:dst" for the destination
createEmptySrcDirs - create empty src directories on destination if set
-See the copy command command for more information on the above.
+See the copy command for more information on the above.
Authentication is required for this call.
sync/move: move a directory from source remote to destination remote
This takes the following parameters:
@@ -6228,7 +6605,7 @@ sync/move: move a directory from source remote to destination
createEmptySrcDirs - create empty src directories on destination if set
deleteEmptySrcDirs - delete empty src directories if set
-See the move command command for more information on the above.
+See the move command for more information on the above.
Authentication is required for this call.
sync/sync: sync a directory from source remote to destination remote
This takes the following parameters:
@@ -6237,7 +6614,7 @@ sync/sync: sync a directory from source remote to destination
dstFs - a remote name string e.g. "drive:dst" for the destination
createEmptySrcDirs - create empty src directories on destination if set
-See the sync command command for more information on the above.
+See the sync command for more information on the above.
Authentication is required for this call.
vfs/forget: Forget files or directories in the directory cache.
This forgets the paths in the directory cache causing them to be re-read from the remote when needed.
@@ -6428,320 +6805,378 @@ Features
Case Insensitive |
Duplicate Files |
MIME Type |
+Metadata |
| 1Fichier |
Whirlpool |
-No |
+- |
No |
Yes |
R |
+- |
| Akamai Netstorage |
MD5, SHA256 |
-Yes |
+R/W |
No |
No |
R |
+- |
| Amazon Drive |
MD5 |
-No |
+- |
Yes |
No |
R |
+- |
| Amazon S3 (or S3 compatible) |
MD5 |
-Yes |
+R/W |
No |
No |
R/W |
+RWU |
| Backblaze B2 |
SHA1 |
-Yes |
+R/W |
No |
No |
R/W |
+- |
| Box |
SHA1 |
-Yes |
+R/W |
Yes |
No |
- |
+- |
| Citrix ShareFile |
MD5 |
-Yes |
+R/W |
Yes |
No |
- |
+- |
| Dropbox |
DBHASH ¹ |
-Yes |
+R |
Yes |
No |
- |
+- |
| Enterprise File Fabric |
- |
-Yes |
+R/W |
Yes |
No |
R/W |
+- |
| FTP |
- |
+R/W ¹⁰ |
No |
No |
-No |
+- |
- |
| Google Cloud Storage |
MD5 |
-Yes |
+R/W |
No |
No |
R/W |
+- |
| Google Drive |
MD5 |
-Yes |
+R/W |
No |
Yes |
R/W |
+- |
| Google Photos |
- |
-No |
+- |
No |
Yes |
R |
+- |
| HDFS |
- |
-Yes |
+R/W |
No |
No |
- |
+- |
+| HiDrive |
+HiDrive ¹² |
+R/W |
+No |
+No |
+- |
+- |
+
+
| HTTP |
- |
-No |
+R |
No |
No |
R |
+- |
-
+
| Hubic |
MD5 |
-Yes |
+R/W |
No |
No |
R/W |
+- |
+
+
+| Internet Archive |
+MD5, SHA1, CRC32 |
+R/W ¹¹ |
+No |
+No |
+- |
+RWU |
| Jottacloud |
MD5 |
-Yes |
+R/W |
Yes |
No |
R |
+- |
| Koofr |
MD5 |
-No |
+- |
Yes |
No |
- |
+- |
| Mail.ru Cloud |
Mailru ⁶ |
-Yes |
+R/W |
Yes |
No |
- |
+- |
| Mega |
- |
-No |
+- |
No |
Yes |
- |
+- |
| Memory |
MD5 |
-Yes |
+R/W |
No |
No |
- |
+- |
| Microsoft Azure Blob Storage |
MD5 |
-Yes |
+R/W |
No |
No |
R/W |
+- |
| Microsoft OneDrive |
SHA1 ⁵ |
-Yes |
+R/W |
Yes |
No |
R |
+- |
| OpenDrive |
MD5 |
-Yes |
+R/W |
Yes |
Partial ⁸ |
- |
+- |
| OpenStack Swift |
MD5 |
-Yes |
+R/W |
No |
No |
R/W |
+- |
| pCloud |
MD5, SHA1 ⁷ |
-Yes |
+R |
No |
No |
W |
+- |
| premiumize.me |
- |
-No |
+- |
Yes |
No |
R |
+- |
| put.io |
CRC-32 |
-Yes |
+R/W |
No |
Yes |
R |
+- |
| QingStor |
MD5 |
-No |
+- ⁹ |
No |
No |
R/W |
+- |
| Seafile |
- |
-No |
+- |
No |
No |
- |
+- |
| SFTP |
MD5, SHA1 ² |
-Yes |
+R/W |
Depends |
No |
- |
+- |
| Sia |
- |
-No |
+- |
No |
No |
- |
+- |
| SugarSync |
- |
+- |
No |
No |
-No |
+- |
- |
| Storj |
- |
-Yes |
+R |
No |
No |
- |
+- |
| Uptobox |
- |
-No |
+- |
No |
Yes |
- |
+- |
| WebDAV |
MD5, SHA1 ³ |
-Yes ⁴ |
+R ⁴ |
Depends |
No |
- |
+- |
| Yandex Disk |
MD5 |
-Yes |
+R/W |
No |
No |
R |
+- |
| Zoho WorkDrive |
- |
-No |
+- |
No |
No |
- |
+- |
| The local filesystem |
All |
-Yes |
+R/W |
Depends |
No |
- |
+RWU |
@@ -6754,12 +7189,18 @@ Notes
⁶ Mail.ru uses its own modified SHA1 hash
⁷ pCloud only supports SHA1 (not MD5) in its EU region
⁸ Opendrive does not support creation of duplicate files using their web client interface or other stock clients, but the underlying storage platform has been determined to allow duplicate files, and it is possible to create them with rclone. It may be that this is a mistake or an unsupported feature.
+⁹ QingStor does not support SetModTime for objects bigger than 5 GiB.
+¹⁰ FTP supports modtimes for the major FTP servers, and also others if they advertised required protocol extensions. See this for more details.
+¹¹ Internet Archive requires option wait_archive to be set to a non-zero value for full modtime support.
+¹² HiDrive supports its own custom hash. It combines SHA1 sums for each 4 KiB block hierarchically to a single top-level sum.
Hash
The cloud storage system supports various hash types of the objects. The hashes are used when transferring data as an integrity check and can be specifically used with the --checksum flag in syncs and in the check command.
To use the verify checksums when transferring between cloud storage systems they must support a common hash type.
ModTime
-The cloud storage system supports setting modification times on objects. If it does then this enables a using the modification times as part of the sync. If not then only the size will be checked by default, though the MD5SUM can be checked with the --checksum flag.
-All cloud storage systems support some kind of date on the object and these will be set when transferring from the cloud storage system.
+Allmost all cloud storage systems store some sort of timestamp on objects, but several of them not something that is appropriate to use for syncing. E.g. some backends will only write a timestamp that represent the time of the upload. To be relevant for syncing it should be able to store the modification time of the source object. If this is not the case, rclone will only check the file size by default, though can be configured to check the file hash (with the --checksum flag). Ideally it should also be possible to change the timestamp of an existing file without having to re-upload it.
+Storage systems with a - in the ModTime column, means the modification read on objects is not the modification time of the file when uploaded. It is most likely the time the file was uploaded, or possibly something else (like the time the picture was taken in Google Photos).
+Storage systems with a R (for read-only) in the ModTime column, means the it keeps modification times on objects, and updates them when uploading objects, but it does not support changing only the modification time (SetModTime operation) without re-uploading, possibly not even without deleting existing first. Some operations in rclone, such as copy and sync commands, will automatically check for SetModTime support and re-upload if necessary to keep the modification times in sync. Other commands will not work without SetModTime support, e.g. touch command on an existing file will fail, and changes to modification time only on a files in a mount will be silently ignored.
+Storage systems with R/W (for read/write) in the ModTime column, means they do also support modtime-only operations.
Case Insensitive
If a cloud storage systems is case sensitive then it is possible to have two files which differ only in case, e.g. file.txt and FILE.txt. If a cloud storage system is case insensitive then that isn't possible.
This can cause problems when syncing between a case insensitive system and a case sensitive system. The symptom of this is that no matter how many times you run the sync it never completes fully.
@@ -7000,120 +7441,158 @@ Encoding option
The --backend-encoding flags allow you to change that. You can disable the encoding completely with --backend-encoding None or set encoding = None in the config file.
Encoding takes a comma separated list of encodings. You can see the list of all possible values by passing an invalid value to this flag, e.g. --local-encoding "help". The command rclone help flags encoding will show you the defaults for the backends.
+
+
+
+
+
| Asterisk |
* |
+* |
| BackQuote |
` |
+` |
| BackSlash |
\ |
+\ |
| Colon |
: |
+: |
| CrLf |
CR 0x0D, LF 0x0A |
+␍, ␊ |
| Ctl |
All control characters 0x00-0x1F |
+␀␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟ |
| Del |
DEL 0x7F |
+␡ |
| Dollar |
$ |
+$ |
| Dot |
. or .. as entire string |
+., .. |
| DoubleQuote |
" |
+" |
| Hash |
# |
+# |
| InvalidUtf8 |
An invalid UTF-8 character (e.g. latin1) |
+� |
| LeftCrLfHtVt |
-CR 0x0D, LF 0x0A,HT 0x09, VT 0x0B on the left of a string |
+CR 0x0D, LF 0x0A, HT 0x09, VT 0x0B on the left of a string |
+␍, ␊, ␉, ␋ |
| LeftPeriod |
. on the left of a string |
+. |
| LeftSpace |
SPACE on the left of a string |
+␠ |
| LeftTilde |
~ on the left of a string |
+~ |
| LtGt |
<, > |
+<, > |
| None |
No characters are encoded |
+ |
| Percent |
% |
+% |
| Pipe |
| |
+| |
| Question |
? |
+? |
| RightCrLfHtVt |
CR 0x0D, LF 0x0A, HT 0x09, VT 0x0B on the right of a string |
+␍, ␊, ␉, ␋ |
| RightPeriod |
. on the right of a string |
+. |
| RightSpace |
SPACE on the right of a string |
+␠ |
+| Semicolon |
+; |
+; |
+
+
| SingleQuote |
' |
+' |
-
+
| Slash |
/ |
+/ |
-
+
| SquareBracket |
[, ] |
+[, ] |
@@ -7139,6 +7618,32 @@ MIME Type
Some cloud storage systems support reading (R) the MIME type of objects and some support writing (W) the MIME type of objects.
The MIME type can be important if you are serving files directly to HTTP from the storage system.
If you are copying from a remote which supports reading (R) to a remote which supports writing (W) then rclone will preserve the MIME types. Otherwise they will be guessed from the extension, or the remote itself may assign the MIME type.
+
+Backends may or may support reading or writing metadata. They may support reading and writing system metadata (metadata intrinsic to that backend) and/or user metadata (general purpose metadata).
+The levels of metadata support are
+
+
+
+
+
+
+R |
+Read only System Metadata |
+
+
+RW |
+Read and write System Metadata |
+
+
+RWU |
+Read and write System Metadata and read and write User Metadata |
+
+
+
+See the metadata docs for more info.
Optional Features
All rclone remotes support a base command set. Other features depend upon backend-specific capabilities.
@@ -7172,6 +7677,19 @@ Optional Features
Yes |
+| Akamai Netstorage |
+Yes |
+No |
+No |
+No |
+No |
+Yes |
+Yes |
+No |
+No |
+Yes |
+
+
| Amazon Drive |
Yes |
No |
@@ -7184,8 +7702,8 @@ Optional Features
No |
Yes |
-
-| Amazon S3 |
+
+| Amazon S3 (or S3 compatible) |
No |
Yes |
No |
@@ -7197,7 +7715,7 @@ Optional Features
No |
No |
-
+
| Backblaze B2 |
No |
Yes |
@@ -7210,7 +7728,7 @@ Optional Features
No |
No |
-
+
| Box |
Yes |
Yes |
@@ -7223,7 +7741,7 @@ Optional Features
Yes |
Yes |
-
+
| Citrix ShareFile |
Yes |
Yes |
@@ -7236,7 +7754,7 @@ Optional Features
No |
Yes |
-
+
| Dropbox |
Yes |
Yes |
@@ -7249,7 +7767,7 @@ Optional Features
Yes |
Yes |
-
+
| Enterprise File Fabric |
Yes |
Yes |
@@ -7262,7 +7780,7 @@ Optional Features
No |
Yes |
-
+
| FTP |
No |
No |
@@ -7275,7 +7793,7 @@ Optional Features
No |
Yes |
-
+
| Google Cloud Storage |
Yes |
Yes |
@@ -7288,7 +7806,7 @@ Optional Features
No |
No |
-
+
| Google Drive |
Yes |
Yes |
@@ -7301,7 +7819,7 @@ Optional Features
Yes |
Yes |
-
+
| Google Photos |
No |
No |
@@ -7314,7 +7832,7 @@ Optional Features
No |
No |
-
+
| HDFS |
Yes |
No |
@@ -7327,6 +7845,19 @@ Optional Features
Yes |
Yes |
+
+| HiDrive |
+Yes |
+Yes |
+Yes |
+Yes |
+No |
+No |
+Yes |
+No |
+No |
+Yes |
+
| HTTP |
No |
@@ -7354,6 +7885,19 @@ Optional Features
No |
+| Internet Archive |
+No |
+Yes |
+No |
+No |
+Yes |
+Yes |
+No |
+Yes |
+Yes |
+No |
+
+
| Jottacloud |
Yes |
Yes |
@@ -7366,6 +7910,19 @@ Optional Features
Yes |
Yes |
+
+| Koofr |
+Yes |
+Yes |
+Yes |
+Yes |
+No |
+No |
+Yes |
+Yes |
+Yes |
+Yes |
+
| Mail.ru Cloud |
Yes |
@@ -7536,6 +8093,19 @@ Optional Features
Yes |
+| Sia |
+No |
+No |
+No |
+No |
+No |
+No |
+Yes |
+No |
+No |
+Yes |
+
+
| SugarSync |
Yes |
Yes |
@@ -7548,7 +8118,7 @@ Optional Features
No |
Yes |
-
+
| Storj |
Yes † |
No |
@@ -7561,7 +8131,7 @@ Optional Features
No |
No |
-
+
| Uptobox |
No |
Yes |
@@ -7574,7 +8144,7 @@ Optional Features
No |
No |
-
+
| WebDAV |
Yes |
Yes |
@@ -7587,7 +8157,7 @@ Optional Features
Yes |
Yes |
-
+
| Yandex Disk |
Yes |
Yes |
@@ -7600,7 +8170,7 @@ Optional Features
Yes |
Yes |
-
+
| Zoho WorkDrive |
Yes |
Yes |
@@ -7613,7 +8183,7 @@ Optional Features
Yes |
Yes |
-
+
| The local filesystem |
Yes |
No |
@@ -7686,6 +8256,7 @@ Non Backend Flags
--delete-during When synchronizing, delete files during transfer
--delete-excluded Delete files on dest excluded from sync
--disable string Disable a comma separated list of features (use --disable help to see a list)
+ --disable-http-keep-alives Disable HTTP keep-alives and use each connection once.
--disable-http2 Disable HTTP/2 in the global transport
-n, --dry-run Do a trial run with no permanent changes
--dscp string Set DSCP value to connections, value or name, e.g. CS1, LE, DF, AF21
@@ -7695,7 +8266,7 @@ Non Backend Flags
--error-on-no-transfer Sets exit code 9 if no files are transferred, useful in scripts
--exclude stringArray Exclude files matching pattern
--exclude-from stringArray Read exclude patterns from file (use - to read from stdin)
- --exclude-if-present string Exclude directories if filename is present
+ --exclude-if-present stringArray Exclude directories if filename is present
--expect-continue-timeout duration Timeout when using expect / 100-continue in HTTP (default 1s)
--fast-list Use recursive list if available; uses more memory but fewer transactions
--files-from stringArray Read list of source-file names from file (use - to read from stdin)
@@ -7734,6 +8305,8 @@ Non Backend Flags
--max-stats-groups int Maximum number of stats groups to keep in memory, on max oldest is discarded (default 1000)
--max-transfer SizeSuffix Maximum size of data to transfer (default off)
--memprofile string Write memory profile to file
+ -M, --metadata If set, preserve metadata when copying objects
+ --metadata-set stringArray Add metadata key=value when uploading
--min-age Duration Only transfer files older than this in s or suffix ms|s|m|h|d|w|M|y (default off)
--min-size SizeSuffix Only transfer files bigger than this in KiB or suffix B|K|M|G|T|P (default off)
--modify-window duration Max time diff to be considered the same (default 1ns)
@@ -7805,7 +8378,7 @@ Non Backend Flags
--use-json-log Use json log format
--use-mmap Use mmap allocator (see docs)
--use-server-modtime Use server modified time instead of object metadata
- --user-agent string Set the user-agent to a specified string (default "rclone/v1.58.0")
+ --user-agent string Set the user-agent to a specified string (default "rclone/v1.59.0")
-v, --verbose count Print lots more stuff (repeat for more)
Backend Flags
These flags are available for every command. They control the backends and may be set in the config file.
@@ -7854,6 +8427,7 @@ Backend Flags
--b2-memory-pool-use-mmap Whether to use mmap buffers in internal memory pool
--b2-test-mode string A flag string for X-Bz-Test-Mode header for debugging
--b2-upload-cutoff SizeSuffix Cutoff for switching to chunked upload (default 200Mi)
+ --b2-version-at Time Show file versions as they were at the specified time (default off)
--b2-versions Include old versions in directory listings
--box-access-token string Box App Primary Access Token
--box-auth-url string Auth server URL
@@ -7893,6 +8467,7 @@ Backend Flags
--chunker-fail-hard Choose how chunker should handle files with missing or invalid chunks
--chunker-hash-type string Choose how chunker handles hash sums (default "md5")
--chunker-remote string Remote to chunk/unchunk
+ --combine-upstreams SpaceSepList Upstreams for combining
--compress-level int GZIP compression level (-2 to 9) (default -1)
--compress-mode string Compression mode (default "gzip")
--compress-ram-cache-limit SizeSuffix Some remotes don't allow the upload of files with unknown size (default 20Mi)
@@ -7925,6 +8500,7 @@ Backend Flags
--drive-list-chunk int Size of listing chunk 100-1000, 0 to disable (default 1000)
--drive-pacer-burst int Number of API calls to allow without sleeping (default 100)
--drive-pacer-min-sleep Duration Minimum time to sleep between API calls (default 100ms)
+ --drive-resource-key string Resource key for accessing a link-shared file
--drive-root-folder-id string ID of the root folder
--drive-scope string Scope that rclone should use when requesting access from drive
--drive-server-side-across-configs Allow server-side operations (e.g. copy) to work across different drive configs
@@ -7980,6 +8556,7 @@ Backend Flags
--ftp-disable-epsv Disable using EPSV even if server advertises support
--ftp-disable-mlsd Disable using MLSD even if server advertises support
--ftp-disable-tls13 Disable TLS 1.3 (workaround for FTP servers with buggy TLS)
+ --ftp-disable-utf8 Disable using UTF-8 even if server advertises support
--ftp-encoding MultiEncoder The encoding for the backend (default Slash,Del,Ctl,RightSpace,Dot)
--ftp-explicit-tls Use Explicit FTPS (FTP over TLS)
--ftp-host string FTP host to connect to
@@ -7998,8 +8575,10 @@ Backend Flags
--gcs-bucket-policy-only Access checks should use bucket-level IAM policies
--gcs-client-id string OAuth Client Id
--gcs-client-secret string OAuth Client Secret
+ --gcs-decompress If set this will decompress gzip encoded objects
--gcs-encoding MultiEncoder The encoding for the backend (default Slash,CrLf,InvalidUtf8,Dot)
--gcs-location string Location for the newly created buckets
+ --gcs-no-check-bucket If set, don't attempt to check the bucket exists or create it
--gcs-object-acl string Access Control List for new objects
--gcs-project-number string Project number
--gcs-service-account-file string Service Account Credentials JSON file path
@@ -8025,10 +8604,24 @@ Backend Flags
--hdfs-namenode string Hadoop name node and port
--hdfs-service-principal-name string Kerberos service principal name for the namenode
--hdfs-username string Hadoop user name
+ --hidrive-auth-url string Auth server URL
+ --hidrive-chunk-size SizeSuffix Chunksize for chunked uploads (default 48Mi)
+ --hidrive-client-id string OAuth Client Id
+ --hidrive-client-secret string OAuth Client Secret
+ --hidrive-disable-fetching-member-count Do not fetch number of objects in directories unless it is absolutely necessary
+ --hidrive-encoding MultiEncoder The encoding for the backend (default Slash,Dot)
+ --hidrive-endpoint string Endpoint for the service (default "https://api.hidrive.strato.com/2.1")
+ --hidrive-root-prefix string The root/parent folder for all paths (default "/")
+ --hidrive-scope-access string Access permissions that rclone should use when requesting access from HiDrive (default "rw")
+ --hidrive-scope-role string User-level that rclone should use when requesting access from HiDrive (default "user")
+ --hidrive-token string OAuth Access Token as a JSON blob
+ --hidrive-token-url string Token server url
+ --hidrive-upload-concurrency int Concurrency for chunked uploads (default 4)
+ --hidrive-upload-cutoff SizeSuffix Cutoff/Threshold for chunked uploads (default 96Mi)
--http-headers CommaSepList Set HTTP headers for all transactions
--http-no-head Don't use HEAD requests
--http-no-slash Set this if the site doesn't end directories with /
- --http-url string URL of http host to connect to
+ --http-url string URL of HTTP host to connect to
--hubic-auth-url string Auth server URL
--hubic-chunk-size SizeSuffix Above this size files will be chunked into a _segments container (default 5Gi)
--hubic-client-id string OAuth Client Id
@@ -8037,6 +8630,13 @@ Backend Flags
--hubic-no-chunk Don't chunk files during streaming upload
--hubic-token string OAuth Access Token as a JSON blob
--hubic-token-url string Token server url
+ --internetarchive-access-key-id string IAS3 Access Key
+ --internetarchive-disable-checksum Don't ask the server to test against MD5 checksum calculated by rclone (default true)
+ --internetarchive-encoding MultiEncoder The encoding for the backend (default Slash,LtGt,CrLf,Del,Ctl,InvalidUtf8,Dot)
+ --internetarchive-endpoint string IAS3 Endpoint (default "https://s3.us.archive.org")
+ --internetarchive-front-endpoint string Host of InternetArchive Frontend (default "https://archive.org")
+ --internetarchive-secret-access-key string IAS3 Secret Key (password)
+ --internetarchive-wait-archive Duration Timeout for waiting the server's processing tasks (specifically archive and book_op) to finish (default 0s)
--jottacloud-encoding MultiEncoder The encoding for the backend (default Slash,LtGt,DoubleQuote,Colon,Question,Asterisk,Pipe,Del,Ctl,InvalidUtf8,Dot)
--jottacloud-hard-delete Delete files permanently rather than putting them into the trash
--jottacloud-md5-memory-limit SizeSuffix Files bigger than this will be cached on disk to calculate the MD5 if required (default 10Mi)
@@ -8058,7 +8658,7 @@ Backend Flags
--local-no-preallocate Disable preallocation of disk space for transferred files
--local-no-set-modtime Disable setting modtime
--local-no-sparse Disable sparse files for multi-thread downloads
- --local-nounc string Disable UNC (long path names) conversion on Windows
+ --local-nounc Disable UNC (long path names) conversion on Windows
--local-unicode-normalization Apply unicode NFC normalization to paths and filenames
--local-zero-size-links Assume the Stat size of links is zero (and read them instead) (deprecated)
--mailru-check-hash What should copy do if file checksum is mismatched or invalid (default true)
@@ -8079,11 +8679,11 @@ Backend Flags
--netstorage-protocol string Select between HTTP or HTTPS protocol (default "https")
--netstorage-secret string Set the NetStorage account secret/G2O key for authentication (obscured)
-x, --one-file-system Don't cross filesystem boundaries (unix/macOS only)
+ --onedrive-access-scopes SpaceSepList Set scopes to be requested by rclone (default Files.Read Files.ReadWrite Files.Read.All Files.ReadWrite.All Sites.Read.All offline_access)
--onedrive-auth-url string Auth server URL
--onedrive-chunk-size SizeSuffix Chunk size to upload files with - must be multiple of 320k (327,680 bytes) (default 10Mi)
--onedrive-client-id string OAuth Client Id
--onedrive-client-secret string OAuth Client Secret
- --onedrive-disable-site-permission Disable the request for Sites.Read.All permission
--onedrive-drive-id string The ID of the drive to use
--onedrive-drive-type string The type of the drive (personal | business | documentLibrary)
--onedrive-encoding MultiEncoder The encoding for the backend (default Slash,LtGt,DoubleQuote,Colon,Question,Asterisk,Pipe,BackSlash,Del,Ctl,LeftSpace,LeftTilde,RightSpace,RightPeriod,InvalidUtf8,Dot)
@@ -8107,9 +8707,11 @@ Backend Flags
--pcloud-client-secret string OAuth Client Secret
--pcloud-encoding MultiEncoder The encoding for the backend (default Slash,BackSlash,Del,Ctl,InvalidUtf8,Dot)
--pcloud-hostname string Hostname to connect to (default "api.pcloud.com")
+ --pcloud-password string Your pcloud password (obscured)
--pcloud-root-folder-id string Fill in for rclone to use a non root folder as its starting point (default "d0")
--pcloud-token string OAuth Access Token as a JSON blob
--pcloud-token-url string Token server url
+ --pcloud-username string Your pcloud username
--premiumizeme-encoding MultiEncoder The encoding for the backend (default Slash,DoubleQuote,BackSlash,Del,Ctl,InvalidUtf8,Dot)
--putio-encoding MultiEncoder The encoding for the backend (default Slash,BackSlash,Del,Ctl,InvalidUtf8,Dot)
--qingstor-access-key-id string QingStor Access Key ID
@@ -8162,6 +8764,7 @@ Backend Flags
--s3-upload-cutoff SizeSuffix Cutoff for switching to chunked upload (default 200Mi)
--s3-use-accelerate-endpoint If true use the AWS S3 accelerated endpoint
--s3-use-multipart-etag Tristate Whether to use ETag in multipart uploads for verification (default unset)
+ --s3-use-presigned-request Whether to use a presigned request or PutObject for single part uploads
--s3-v2-auth If true use v2 authentication
--seafile-2fa Two-factor authentication ('true' if the account has 2FA enabled)
--seafile-create-library Should rclone create a library if it doesn't exist
@@ -8172,6 +8775,8 @@ Backend Flags
--seafile-url string URL of seafile host to connect to
--seafile-user string User name (usually email address)
--sftp-ask-password Allow asking for SFTP password when needed
+ --sftp-chunk-size SizeSuffix Upload and download chunk size (default 32Ki)
+ --sftp-concurrency int The maximum number of outstanding requests for one file (default 64)
--sftp-disable-concurrent-reads If set don't use concurrent reads
--sftp-disable-concurrent-writes If set don't use concurrent writes
--sftp-disable-hashcheck Disable the execution of SSH commands to determine if remote file hashing is available
@@ -8184,12 +8789,14 @@ Backend Flags
--sftp-known-hosts-file string Optional path to known_hosts file
--sftp-md5sum-command string The command used to read md5 hashes
--sftp-pass string SSH password, leave blank to use ssh-agent (obscured)
- --sftp-path-override string Override path used by SSH connection
+ --sftp-path-override string Override path used by SSH shell commands
--sftp-port int SSH port number (default 22)
--sftp-pubkey-file string Optional path to public key file
--sftp-server-command string Specifies the path or command to run a sftp server on the remote host
+ --sftp-set-env SpaceSepList Environment variables to pass to sftp and commands
--sftp-set-modtime Set the modified time on the remote if set (default true)
--sftp-sha1sum-command string The command used to read sha1 hashes
+ --sftp-shell-type string The type of SSH shell on remote server, if any
--sftp-skip-links Set to skip any symlinks and any other non regular files
--sftp-subsystem string Specifies the SSH2 subsystem on the remote host (default "sftp")
--sftp-use-fstat If set use fstat instead of stat
@@ -8246,6 +8853,7 @@ Backend Flags
--union-action-policy string Policy to choose upstream on ACTION category (default "epall")
--union-cache-time int Cache time of usage and free space (in seconds) (default 120)
--union-create-policy string Policy to choose upstream on CREATE category (default "epmfs")
+ --union-min-free-space SizeSuffix Minimum viable free space for lfs/eplfs policies (default 1Gi)
--union-search-policy string Policy to choose upstream on SEARCH category (default "ff")
--union-upstreams string List of space separated upstreams
--uptobox-access-token string Your access token
@@ -8257,7 +8865,7 @@ Backend Flags
--webdav-pass string Password (obscured)
--webdav-url string URL of http host to connect to
--webdav-user string User name
- --webdav-vendor string Name of the Webdav site/service/software you are using
+ --webdav-vendor string Name of the WebDAV site/service/software you are using
--yandex-auth-url string Auth server URL
--yandex-client-id string OAuth Client Id
--yandex-client-secret string OAuth Client Secret
@@ -8539,7 +9147,7 @@ Command line syntax
-v, --verbose Increases logging verbosity.
May be specified more than once for more details.
-h, --help help for bisync
-Arbitrary rclone flags may be specified on the bisync command line, for example rclone bsync ./testdir/path1/ gdrive:testdir/path2/ --drive-skip-gdocs -v -v --timeout 10s Note that interactions of various rclone flags with bisync process flow has not been fully tested yet.
+Arbitrary rclone flags may be specified on the bisync command line, for example rclone bisync ./testdir/path1/ gdrive:testdir/path2/ --drive-skip-gdocs -v -v --timeout 10s Note that interactions of various rclone flags with bisync process flow has not been fully tested yet.
Paths
Path1 and Path2 arguments may be references to any mix of local directory paths (absolute or relative), UNC paths (//server/share/path), Windows drive paths (with a drive letter and :) or configured remotes with optional subdirectory paths. Cloud references are distinguished by having a : in the argument (see Windows support below).
Path1 and Path2 are treated equally, in that neither has priority for file changes, and access efficiency does not change whether a remote is on Path1 or Path2.
@@ -8715,7 +9323,7 @@ Return codes
rclone bisync returns the following codes to calling program: - 0 on a successful run, - 1 for a non-critical failing run (a rerun may be successful), - 2 for a critically aborted run (requires a --resync to recover).
Limitations
Supported backends
-Bisync is considered BETA and has been tested with the following backends: - Local filesystem - Google Drive - Dropbox - OneDrive - S3 - SFTP
+Bisync is considered BETA and has been tested with the following backends: - Local filesystem - Google Drive - Dropbox - OneDrive - S3 - SFTP - Yandex Disk
It has not been fully tested with other services yet. If it works, or sorta works, please let us know and we'll update the list. Run the test suite to check for proper operation as described below.
First release of rclone bisync requires that underlying backend supported the modification time feature and will refuse to run otherwise. This limitation will be lifted in a future rclone bisync release.
Concurrent modifications
@@ -8883,7 +9491,7 @@ Retries
Denied downloads of "infected" or "abusive" files
Google Drive has a filter for certain file types (.exe, .apk, et cetera) that by default cannot be copied from Google Drive to the local filesystem. If you are having problems, run with --verbose to see specifically which files are generating complaints. If the error is This file has been identified as malware or spam and cannot be downloaded, consider using the flag --drive-acknowledge-abuse.
Google Doc files
-Google docs exist as virtual files on Google Drive and cannot be transferred to other filesystems natively. While it is possible to export a Google doc to a normal file (with .xlsx extension, for example), it's not possible to import a normal file back into a Google document.
+Google docs exist as virtual files on Google Drive and cannot be transferred to other filesystems natively. While it is possible to export a Google doc to a normal file (with .xlsx extension, for example), it is not possible to import a normal file back into a Google document.
Bisync's handling of Google Doc files is to flag them in the run log output for user's attention and ignore them for any file transfers, deletes, or syncs. They will show up with a length of -1 in the listings. This bisync run is otherwise successful:
2021/05/11 08:23:15 INFO : Synching Path1 "/path/to/local/tree/base/" with Path2 "GDrive:"
2021/05/11 08:23:15 INFO : ...path2.lst-new: Ignoring incorrect line: "- -1 - - 2018-07-29T08:49:30.136000000+0000 GoogleDoc.docx"
@@ -9223,7 +9831,7 @@ Restricted filename characters
Invalid UTF-8 bytes will also be replaced, as they can't be used in JSON strings.
Standard options
-Here are the standard options specific to fichier (1Fichier).
+Here are the Standard options specific to fichier (1Fichier).
--fichier-api-key
Your API Key, get it from https://1fichier.com/console/params.pl.
Properties:
@@ -9234,7 +9842,7 @@ --fichier-api-key
Required: false
Advanced options
-Here are the advanced options specific to fichier (1Fichier).
+Here are the Advanced options specific to fichier (1Fichier).
--fichier-shared-folder
If you want to download a shared folder, add this parameter.
Properties:
@@ -9276,7 +9884,7 @@ --fichier-encoding
Limitations
rclone about is not supported by the 1Fichier backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Alias
The alias remote provides a new name for another remote.
Paths may be as deep as required or a local path, e.g. remote:directory/subdirectory or /directory/subdirectory.
@@ -9334,7 +9942,7 @@ Configuration
Copy another local directory to the alias directory called source
rclone copy /home/source remote:source
Standard options
-Here are the standard options specific to alias (Alias for an existing remote).
+Here are the Standard options specific to alias (Alias for an existing remote).
--alias-remote
Remote or path to alias.
Can be "myremote:path/to/dir", "myremote:bucket", "myremote:" or "/local/path".
@@ -9446,7 +10054,7 @@ Deleting files
Let's say you usually use amazon.co.uk. When you authenticate with rclone it will take you to an amazon.com page to log in. Your amazon.co.uk email and password should work here just fine.
Standard options
-Here are the standard options specific to amazon cloud drive (Amazon Drive).
+Here are the Standard options specific to amazon cloud drive (Amazon Drive).
--acd-client-id
OAuth Client Id.
Leave blank normally.
@@ -9468,7 +10076,7 @@ --acd-client-secret
Required: false
Advanced options
-Here are the advanced options specific to amazon cloud drive (Amazon Drive).
+Here are the Advanced options specific to amazon cloud drive (Amazon Drive).
--acd-token
OAuth Access Token as a JSON blob.
Properties:
@@ -9549,16 +10157,21 @@ Limitations
At the time of writing (Jan 2016) is in the area of 50 GiB per file. This means that larger files are likely to fail.
Unfortunately there is no way for rclone to see that this failure is because of file size, so it will retry the operation, as any other failure. To avoid this problem, use --max-size 50000M option to limit the maximum size of uploaded files. Note that --max-size does not split files into segments, it only ignores files over this size.
rclone about is not supported by the Amazon Drive backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Amazon S3 Storage Providers
The S3 backend can be used with a number of different providers:
- AWS S3
- Alibaba Cloud (Aliyun) Object Storage System (OSS)
- Ceph
+- China Mobile Ecloud Elastic Object Storage (EOS)
+- Cloudflare R2
+- Arvan Cloud Object Storage (AOS)
- DigitalOcean Spaces
- Dreamhost
+- Huawei OBS
- IBM COS S3
+- IDrive e2
- Minio
- RackCorp Object Storage
- Scaleway
@@ -9593,7 +10206,7 @@ Configuration
Type of storage to configure.
Choose a number from below, or type in your own value
[snip]
-XX / Amazon S3 Compliant Storage Providers including AWS, Ceph, Dreamhost, IBM COS, Minio, and Tencent COS
+XX / Amazon S3 Compliant Storage Providers including AWS, Ceph, ChinaMobile, ArvanCloud, Dreamhost, IBM COS, Minio, and Tencent COS
\ "s3"
[snip]
Storage> s3
@@ -9979,7 +10592,7 @@ Object-lock enabled S3 bucket
As mentioned in the Hashes section, small files that are not uploaded as multipart, use a different tag, causing the upload to fail. A simple solution is to set the --s3-upload-cutoff 0 and force all the files to be uploaded as multipart.
Standard options
-Here are the standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
+Here are the Standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, IDrive e2, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi).
--s3-provider
Choose your S3 provider.
Properties:
@@ -10002,6 +10615,18 @@ --s3-provider
+- "ChinaMobile"
+
+- China Mobile Ecloud Elastic Object Storage (EOS)
+
+- "Cloudflare"
+
+- "ArvanCloud"
+
+- Arvan Cloud Object Storage (AOS)
+
- "DigitalOcean"
- Digital Ocean Spaces
@@ -10010,10 +10635,18 @@ --s3-provider
+- "HuaweiOBS"
+
+- Huawei Object Storage Service
+
- "IBMCOS"
+- "IDrive"
+
- "LyveCloud"
- Seagate Lyve Cloud
@@ -10348,76 +10981,326 @@ --s3-region
+- "pl-waw"
+
--s3-region
-Region to connect to.
-Leave blank if you are using an S3 clone and you don't have a region.
+Region to connect to. - the location where your bucket will be created and your data stored. Need bo be same with your endpoint.
Properties:
- Config: region
- Env Var: RCLONE_S3_REGION
-- Provider: !AWS,Alibaba,RackCorp,Scaleway,Storj,TencentCOS
+- Provider: HuaweiOBS
- Type: string
- Required: false
- Examples:
-- ""
+
- "af-south-1"
-- Use this if unsure.
-- Will use v4 signatures and an empty region.
+- AF-Johannesburg
-- "other-v2-signature"
+
- "ap-southeast-2"
-- Use this only if v4 signatures don't work.
-- E.g. pre Jewel/v10 CEPH.
+- AP-Bangkok
+- "ap-southeast-3"
+
-
---s3-endpoint
-Endpoint for S3 API.
-Leave blank if using AWS to use the default endpoint for the region.
-Properties:
+ - "cn-east-3"
-- Config: endpoint
-- Env Var: RCLONE_S3_ENDPOINT
-- Provider: AWS
-- Type: string
-- Required: false
-
---s3-endpoint
-Endpoint for IBM COS S3 API.
-Specify if using an IBM COS On Premise.
-Properties:
+ - CN East-Shanghai1
+
+"cn-east-2"
-- Config: endpoint
-- Env Var: RCLONE_S3_ENDPOINT
-- Provider: IBMCOS
-- Type: string
-- Required: false
-- Examples:
+
- CN East-Shanghai2
+
+"cn-north-1"
-- "s3.us.cloud-object-storage.appdomain.cloud"
+
- CN North-Beijing1
+
+"cn-north-4"
-- US Cross Region Endpoint
+- CN North-Beijing4
-"s3.dal.us.cloud-object-storage.appdomain.cloud"
+"cn-south-1"
-- US Cross Region Dallas Endpoint
+- CN South-Guangzhou
-"s3.wdc.us.cloud-object-storage.appdomain.cloud"
+"ap-southeast-1"
-- US Cross Region Washington DC Endpoint
+- CN-Hong Kong
-"s3.sjc.us.cloud-object-storage.appdomain.cloud"
+"sa-argentina-1"
-- US Cross Region San Jose Endpoint
+- LA-Buenos Aires1
-"s3.private.us.cloud-object-storage.appdomain.cloud"
+"sa-peru-1"
-- US Cross Region Private Endpoint
+- LA-Lima1
-"s3.private.dal.us.cloud-object-storage.appdomain.cloud"
+"na-mexico-1"
+
+"sa-chile-1"
+
+"sa-brazil-1"
+
+"ru-northwest-2"
+
+
+
+--s3-region
+Region to connect to.
+Properties:
+
+- Config: region
+- Env Var: RCLONE_S3_REGION
+- Provider: Cloudflare
+- Type: string
+- Required: false
+- Examples:
+
+- "auto"
+
+- R2 buckets are automatically distributed across Cloudflare's data centers for low latency.
+
+
+
+--s3-region
+Region to connect to.
+Leave blank if you are using an S3 clone and you don't have a region.
+Properties:
+
+- Config: region
+- Env Var: RCLONE_S3_REGION
+- Provider: !AWS,Alibaba,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,Storj,TencentCOS,HuaweiOBS,IDrive
+- Type: string
+- Required: false
+- Examples:
+
+- ""
+
+- Use this if unsure.
+- Will use v4 signatures and an empty region.
+
+- "other-v2-signature"
+
+- Use this only if v4 signatures don't work.
+- E.g. pre Jewel/v10 CEPH.
+
+
+
+--s3-endpoint
+Endpoint for S3 API.
+Leave blank if using AWS to use the default endpoint for the region.
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_S3_ENDPOINT
+- Provider: AWS
+- Type: string
+- Required: false
+
+--s3-endpoint
+Endpoint for China Mobile Ecloud Elastic Object Storage (EOS) API.
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_S3_ENDPOINT
+- Provider: ChinaMobile
+- Type: string
+- Required: false
+- Examples:
+
+- "eos-wuxi-1.cmecloud.cn"
+
+- The default endpoint - a good choice if you are unsure.
+- East China (Suzhou)
+
+- "eos-jinan-1.cmecloud.cn"
+
+- "eos-ningbo-1.cmecloud.cn"
+
+- "eos-shanghai-1.cmecloud.cn"
+
+- East China (Shanghai-1)
+
+- "eos-zhengzhou-1.cmecloud.cn"
+
+- Central China (Zhengzhou)
+
+- "eos-hunan-1.cmecloud.cn"
+
+- Central China (Changsha-1)
+
+- "eos-zhuzhou-1.cmecloud.cn"
+
+- Central China (Changsha-2)
+
+- "eos-guangzhou-1.cmecloud.cn"
+
+- South China (Guangzhou-2)
+
+- "eos-dongguan-1.cmecloud.cn"
+
+- South China (Guangzhou-3)
+
+- "eos-beijing-1.cmecloud.cn"
+
+- North China (Beijing-1)
+
+- "eos-beijing-2.cmecloud.cn"
+
+- North China (Beijing-2)
+
+- "eos-beijing-4.cmecloud.cn"
+
+- North China (Beijing-3)
+
+- "eos-huhehaote-1.cmecloud.cn"
+
+- North China (Huhehaote)
+
+- "eos-chengdu-1.cmecloud.cn"
+
+- Southwest China (Chengdu)
+
+- "eos-chongqing-1.cmecloud.cn"
+
+- Southwest China (Chongqing)
+
+- "eos-guiyang-1.cmecloud.cn"
+
+- Southwest China (Guiyang)
+
+- "eos-xian-1.cmecloud.cn"
+
+- Nouthwest China (Xian)
+
+- "eos-yunnan.cmecloud.cn"
+
+- Yunnan China (Kunming)
+
+- "eos-yunnan-2.cmecloud.cn"
+
+- Yunnan China (Kunming-2)
+
+- "eos-tianjin-1.cmecloud.cn"
+
+- Tianjin China (Tianjin)
+
+- "eos-jilin-1.cmecloud.cn"
+
+- Jilin China (Changchun)
+
+- "eos-hubei-1.cmecloud.cn"
+
+- Hubei China (Xiangyan)
+
+- "eos-jiangxi-1.cmecloud.cn"
+
+- Jiangxi China (Nanchang)
+
+- "eos-gansu-1.cmecloud.cn"
+
+- "eos-shanxi-1.cmecloud.cn"
+
+- Shanxi China (Taiyuan)
+
+- "eos-liaoning-1.cmecloud.cn"
+
+- Liaoning China (Shenyang)
+
+- "eos-hebei-1.cmecloud.cn"
+
+- Hebei China (Shijiazhuang)
+
+- "eos-fujian-1.cmecloud.cn"
+
+- "eos-guangxi-1.cmecloud.cn"
+
+- Guangxi China (Nanning)
+
+- "eos-anhui-1.cmecloud.cn"
+
+
+
+--s3-endpoint
+Endpoint for Arvan Cloud Object Storage (AOS) API.
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_S3_ENDPOINT
+- Provider: ArvanCloud
+- Type: string
+- Required: false
+- Examples:
+
+- "s3.ir-thr-at1.arvanstorage.com"
+
+- The default endpoint - a good choice if you are unsure.
+- Tehran Iran (Asiatech)
+
+- "s3.ir-tbz-sh1.arvanstorage.com"
+
+- Tabriz Iran (Shahriar)
+
+
+
+--s3-endpoint
+Endpoint for IBM COS S3 API.
+Specify if using an IBM COS On Premise.
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_S3_ENDPOINT
+- Provider: IBMCOS
+- Type: string
+- Required: false
+- Examples:
+
+- "s3.us.cloud-object-storage.appdomain.cloud"
+
+- US Cross Region Endpoint
+
+- "s3.dal.us.cloud-object-storage.appdomain.cloud"
+
+- US Cross Region Dallas Endpoint
+
+- "s3.wdc.us.cloud-object-storage.appdomain.cloud"
+
+- US Cross Region Washington DC Endpoint
+
+- "s3.sjc.us.cloud-object-storage.appdomain.cloud"
+
+- US Cross Region San Jose Endpoint
+
+- "s3.private.us.cloud-object-storage.appdomain.cloud"
+
+- US Cross Region Private Endpoint
+
+- "s3.private.dal.us.cloud-object-storage.appdomain.cloud"
- US Cross Region Dallas Private Endpoint
@@ -10647,7 +11530,7 @@ --s3-endpoint
---s3-endpoint
+--s3-endpoint
Endpoint for OSS API.
Properties:
@@ -10760,7 +11643,80 @@ --s3-endpoint
---s3-endpoint
+--s3-endpoint
+Endpoint for OBS API.
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_S3_ENDPOINT
+- Provider: HuaweiOBS
+- Type: string
+- Required: false
+- Examples:
+
+- "obs.af-south-1.myhuaweicloud.com"
+
+- "obs.ap-southeast-2.myhuaweicloud.com"
+
+- "obs.ap-southeast-3.myhuaweicloud.com"
+
+- "obs.cn-east-3.myhuaweicloud.com"
+
+- "obs.cn-east-2.myhuaweicloud.com"
+
+- "obs.cn-north-1.myhuaweicloud.com"
+
+- "obs.cn-north-4.myhuaweicloud.com"
+
+- "obs.cn-south-1.myhuaweicloud.com"
+
+- "obs.ap-southeast-1.myhuaweicloud.com"
+
+- "obs.sa-argentina-1.myhuaweicloud.com"
+
+- "obs.sa-peru-1.myhuaweicloud.com"
+
+- "obs.na-mexico-1.myhuaweicloud.com"
+
+- "obs.sa-chile-1.myhuaweicloud.com"
+
+- "obs.sa-brazil-1.myhuaweicloud.com"
+
+- "obs.ru-northwest-2.myhuaweicloud.com"
+
+
+
+--s3-endpoint
Endpoint for Scaleway Object Storage.
Properties:
@@ -10779,9 +11735,13 @@ --s3-endpoint
+- "s3.pl-waw.scw.cloud"
+
---s3-endpoint
+--s3-endpoint
Endpoint for StackPath Object Storage.
Properties:
@@ -10806,7 +11766,7 @@ --s3-endpoint
---s3-endpoint
+--s3-endpoint
Endpoint of the Shared Gateway.
Properties:
@@ -10831,7 +11791,7 @@ --s3-endpoint
---s3-endpoint
+--s3-endpoint
Endpoint for Tencent COS API.
Properties:
@@ -10920,7 +11880,7 @@ --s3-endpoint
---s3-endpoint
+--s3-endpoint
Endpoint for RackCorp Object Storage.
Properties:
@@ -11009,14 +11969,14 @@ --s3-endpoint
---s3-endpoint
+--s3-endpoint
Endpoint for S3 API.
Required when using an S3 clone.
Properties:
- Config: endpoint
- Env Var: RCLONE_S3_ENDPOINT
-- Provider: !AWS,IBMCOS,TencentCOS,Alibaba,Scaleway,StackPath,Storj,RackCorp
+- Provider: !AWS,IBMCOS,IDrive,TencentCOS,HuaweiOBS,Alibaba,ChinaMobile,ArvanCloud,Scaleway,StackPath,Storj,RackCorp
- Type: string
- Required: false
- Examples:
@@ -11073,6 +12033,10 @@
--s3-endpoint
- Wasabi AP Northeast 2 (Osaka) endpoint
+- "s3.ir-thr-at1.arvanstorage.com"
+
+- ArvanCloud Tehran Iran (Asiatech) endpoint
+
--s3-location-constraint
@@ -11190,52 +12154,208 @@ --s3-location-constraint
--s3-location-constraint
-Location constraint - must match endpoint when using IBM Cloud Public.
-For on-prem COS, do not make a selection from this list, hit enter.
+Location constraint - must match endpoint.
+Used when creating buckets only.
Properties:
- Config: location_constraint
- Env Var: RCLONE_S3_LOCATION_CONSTRAINT
-- Provider: IBMCOS
+- Provider: ChinaMobile
- Type: string
- Required: false
- Examples:
-- "us-standard"
+
- "wuxi1"
-- US Cross Region Standard
+- East China (Suzhou)
-- "us-vault"
+
- "jinan1"
-- US Cross Region Vault
+- East China (Jinan)
-- "us-cold"
+
- "ningbo1"
-- US Cross Region Cold
+- East China (Hangzhou)
-- "us-flex"
+
- "shanghai1"
-- US Cross Region Flex
+- East China (Shanghai-1)
-- "us-east-standard"
+
- "zhengzhou1"
-- US East Region Standard
+- Central China (Zhengzhou)
-- "us-east-vault"
+
- "hunan1"
-- US East Region Vault
+- Central China (Changsha-1)
-- "us-east-cold"
+
- "zhuzhou1"
-- US East Region Cold
+- Central China (Changsha-2)
-- "us-east-flex"
+
- "guangzhou1"
-- US East Region Flex
+- South China (Guangzhou-2)
-- "us-south-standard"
+
- "dongguan1"
-- US South Region Standard
+- South China (Guangzhou-3)
+
+- "beijing1"
+
+- North China (Beijing-1)
+
+- "beijing2"
+
+- North China (Beijing-2)
+
+- "beijing4"
+
+- North China (Beijing-3)
+
+- "huhehaote1"
+
+- North China (Huhehaote)
+
+- "chengdu1"
+
+- Southwest China (Chengdu)
+
+- "chongqing1"
+
+- Southwest China (Chongqing)
+
+- "guiyang1"
+
+- Southwest China (Guiyang)
+
+- "xian1"
+
+- Nouthwest China (Xian)
+
+- "yunnan"
+
+- Yunnan China (Kunming)
+
+- "yunnan2"
+
+- Yunnan China (Kunming-2)
+
+- "tianjin1"
+
+- Tianjin China (Tianjin)
+
+- "jilin1"
+
+- Jilin China (Changchun)
+
+- "hubei1"
+
+- Hubei China (Xiangyan)
+
+- "jiangxi1"
+
+- Jiangxi China (Nanchang)
+
+- "gansu1"
+
+- "shanxi1"
+
+- Shanxi China (Taiyuan)
+
+- "liaoning1"
+
+- Liaoning China (Shenyang)
+
+- "hebei1"
+
+- Hebei China (Shijiazhuang)
+
+- "fujian1"
+
+- "guangxi1"
+
+- Guangxi China (Nanning)
+
+- "anhui1"
+
+
+
+--s3-location-constraint
+Location constraint - must match endpoint.
+Used when creating buckets only.
+Properties:
+
+- Config: location_constraint
+- Env Var: RCLONE_S3_LOCATION_CONSTRAINT
+- Provider: ArvanCloud
+- Type: string
+- Required: false
+- Examples:
+
+- "ir-thr-at1"
+
+- Tehran Iran (Asiatech)
+
+- "ir-tbz-sh1"
+
+- Tabriz Iran (Shahriar)
+
+
+
+--s3-location-constraint
+Location constraint - must match endpoint when using IBM Cloud Public.
+For on-prem COS, do not make a selection from this list, hit enter.
+Properties:
+
+- Config: location_constraint
+- Env Var: RCLONE_S3_LOCATION_CONSTRAINT
+- Provider: IBMCOS
+- Type: string
+- Required: false
+- Examples:
+
+- "us-standard"
+
+- US Cross Region Standard
+
+- "us-vault"
+
+- "us-cold"
+
+- "us-flex"
+
+- "us-east-standard"
+
+- US East Region Standard
+
+- "us-east-vault"
+
+- "us-east-cold"
+
+- "us-east-flex"
+
+- "us-south-standard"
+
+- US South Region Standard
- "us-south-vault"
@@ -11331,7 +12451,7 @@ --s3-location-constraint
---s3-location-constraint
+--s3-location-constraint
Location constraint - the location where your bucket will be located and your data stored.
Properties:
@@ -11420,14 +12540,14 @@ --s3-location-constraint
---s3-location-constraint
+--s3-location-constraint
Location constraint - must be set to match the Region.
Leave blank if not sure. Used when creating buckets only.
Properties:
- Config: location_constraint
- Env Var: RCLONE_S3_LOCATION_CONSTRAINT
-- Provider: !AWS,IBMCOS,Alibaba,RackCorp,Scaleway,StackPath,Storj,TencentCOS
+- Provider: !AWS,IBMCOS,IDrive,Alibaba,HuaweiOBS,ChinaMobile,Cloudflare,ArvanCloud,RackCorp,Scaleway,StackPath,Storj,TencentCOS
- Type: string
- Required: false
@@ -11440,7 +12560,7 @@ --s3-acl
- Config: acl
- Env Var: RCLONE_S3_ACL
-- Provider: !Storj
+- Provider: !Storj,Cloudflare
- Type: string
- Required: false
- Examples:
@@ -11515,7 +12635,7 @@
--s3-server-side-encryption
- Config: server_side_encryption
- Env Var: RCLONE_S3_SERVER_SIDE_ENCRYPTION
-- Provider: AWS,Ceph,Minio
+- Provider: AWS,Ceph,ChinaMobile,Minio
- Type: string
- Required: false
- Examples:
@@ -11634,6 +12754,52 @@
--s3-storage-class
--s3-storage-class
+The storage class to use when storing new objects in ChinaMobile.
+Properties:
+
+- Config: storage_class
+- Env Var: RCLONE_S3_STORAGE_CLASS
+- Provider: ChinaMobile
+- Type: string
+- Required: false
+- Examples:
+
+- ""
+
+- "STANDARD"
+
+- Standard storage class
+
+- "GLACIER"
+
+- "STANDARD_IA"
+
+- Infrequent access storage mode
+
+
+
+--s3-storage-class
+The storage class to use when storing new objects in ArvanCloud.
+Properties:
+
+- Config: storage_class
+- Env Var: RCLONE_S3_STORAGE_CLASS
+- Provider: ArvanCloud
+- Type: string
+- Required: false
+- Examples:
+
+- "STANDARD"
+
+- Standard storage class
+
+
+
+--s3-storage-class
The storage class to use when storing new objects in Tencent COS.
Properties:
@@ -11662,7 +12828,7 @@ --s3-storage-class
---s3-storage-class
+--s3-storage-class
The storage class to use when storing new objects in S3.
Properties:
@@ -11690,7 +12856,7 @@ --s3-storage-class
Advanced options
-Here are the advanced options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS).
+Here are the Advanced options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, IDrive e2, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi).
--s3-bucket-acl
Canned ACL used when creating buckets.
For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
@@ -11742,7 +12908,7 @@ --s3-sse-customer-algorithm
- Config: sse_customer_algorithm
- Env Var: RCLONE_S3_SSE_CUSTOMER_ALGORITHM
-- Provider: AWS,Ceph,Minio
+- Provider: AWS,Ceph,ChinaMobile,Minio
- Type: string
- Required: false
- Examples:
@@ -11763,7 +12929,7 @@
--s3-sse-customer-key
- Config: sse_customer_key
- Env Var: RCLONE_S3_SSE_CUSTOMER_KEY
-- Provider: AWS,Ceph,Minio
+- Provider: AWS,Ceph,ChinaMobile,Minio
- Type: string
- Required: false
- Examples:
@@ -11781,7 +12947,7 @@
--s3-sse-customer-key-md5
- Config: sse_customer_key_md5
- Env Var: RCLONE_S3_SSE_CUSTOMER_KEY_MD5
-- Provider: AWS,Ceph,Minio
+- Provider: AWS,Ceph,ChinaMobile,Minio
- Type: string
- Required: false
- Examples:
@@ -11809,6 +12975,7 @@
--s3-chunk-size
If you are transferring large files over high-speed links and you have enough memory, then increasing this will speed up the transfers.
Rclone will automatically increase the chunk size when uploading a large file of known size to stay below the 10,000 chunks limit.
Files of unknown size are uploaded with the configured chunk_size. Since the default chunk size is 5 MiB and there can be at most 10,000 chunks, this means that by default the maximum size of a file you can stream upload is 48 GiB. If you wish to stream upload larger files then you will need to increase chunk_size.
+Increasing the chunk size decreases the accuracy of the progress statistics displayed with "-P" flag. Rclone treats chunk as sent when it's buffered by the AWS SDK, when in fact it may still be uploading. A bigger chunk size means a bigger AWS SDK buffer and progress reporting more deviating from the truth.
Properties:
- Config: chunk_size
@@ -12073,12 +13240,103 @@ --s3-use-multipart-etag
- Type: Tristate
- Default: unset
+--s3-use-presigned-request
+Whether to use a presigned request or PutObject for single part uploads
+If this is false rclone will use PutObject from the AWS SDK to upload an object.
+Versions of rclone < 1.59 use presigned requests to upload a single part object and setting this flag to true will re-enable that functionality. This shouldn't be necessary except in exceptional circumstances or for testing.
+Properties:
+
+- Config: use_presigned_request
+- Env Var: RCLONE_S3_USE_PRESIGNED_REQUEST
+- Type: bool
+- Default: false
+
+
+User metadata is stored as x-amz-meta- keys. S3 metadata keys are case insensitive and are always returned in lower case.
+Here are the possible system metadata items for the s3 backend.
+
+
+
+
+
+
+
+
+
+
+
+
+
+| btime |
+Time of file birth (creation) read from Last-Modified header |
+RFC 3339 |
+2006-01-02T15:04:05.999999999Z07:00 |
+Y |
+
+
+| cache-control |
+Cache-Control header |
+string |
+no-cache |
+N |
+
+
+| content-disposition |
+Content-Disposition header |
+string |
+inline |
+N |
+
+
+| content-encoding |
+Content-Encoding header |
+string |
+gzip |
+N |
+
+
+| content-language |
+Content-Language header |
+string |
+en-US |
+N |
+
+
+| content-type |
+Content-Type header |
+string |
+text/plain |
+N |
+
+
+| mtime |
+Time of last modification, read from rclone metadata |
+RFC 3339 |
+2006-01-02T15:04:05.999999999Z07:00 |
+N |
+
+
+| tier |
+Tier of the object |
+string |
+GLACIER |
+Y |
+
+
+
+See the metadata docs for more info.
Backend commands
Here are the commands specific to the s3 backend.
Run them with
rclone backend COMMAND remote:
The help below will explain what arguments each command takes.
-See the "rclone backend" command for more info on how to pass options and arguments.
+See the backend command for more info on how to pass options and arguments.
These can be run on a running backend using the rc command backend/command.
restore
Restore objects from GLACIER to normal storage
@@ -12170,7 +13428,9 @@ AWS S3
This is the provider used as main example and described in the configuration section above.
AWS Snowball Edge
AWS Snowball is a hardware appliance used for transferring bulk data back to AWS. Its main software interface is S3 object storage.
-To use rclone with AWS Snowball Edge devices, configure as standard for an 'S3 Compatible Service' be sure to set upload_cutoff = 0 otherwise you will run into authentication header issues as the snowball device does not support query parameter based authentication.
+To use rclone with AWS Snowball Edge devices, configure as standard for an 'S3 Compatible Service'.
+If using rclone pre v1.59 be sure to set upload_cutoff = 0 otherwise you will run into authentication header issues as the snowball device does not support query parameter based authentication.
+With rclone v1.59 or later setting upload_cutoff should not be necessary.
eg.
[snowball]
type = s3
@@ -12194,7 +13454,7 @@ Ceph
acl =
server_side_encryption =
storage_class =
-If you are using an older version of CEPH, e.g. 10.2.x Jewel, then you may need to supply the parameter --s3-upload-cutoff 0 or put this in the config file as upload_cutoff 0 to work around a bug which causes uploading of small files to fail.
+If you are using an older version of CEPH (e.g. 10.2.x Jewel) and a version of rclone before v1.59 then you may need to supply the parameter --s3-upload-cutoff 0 or put this in the config file as upload_cutoff 0 to work around a bug which causes uploading of small files to fail.
Note also that Ceph sometimes puts / in the passwords it gives users. If you read the secret access key using the command line tools you will get a JSON blob with the / escaped as \/. Make sure you only write / in the secret access key.
Eg the dump from Ceph looks something like this (irrelevant keys removed).
{
@@ -12209,6 +13469,86 @@ Ceph
],
}
Because this is a json dump, it is encoding the / as \/, so if you use the secret key as xxxxxx/xxxx it will work fine.
+Cloudflare R2
+Cloudflare R2 Storage allows developers to store large amounts of unstructured data without the costly egress bandwidth fees associated with typical cloud storage services.
+Here is an example of making a Cloudflare R2 configuration. First run:
+rclone config
+This will guide you through an interactive setup process.
+Note that all buckets are private, and all are stored in the same "auto" region. It is necessary to use Cloudflare workers to share the content of a bucket publicly.
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+name> r2
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+...
+XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi
+ \ (s3)
+...
+Storage> s3
+Option provider.
+Choose your S3 provider.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+...
+XX / Cloudflare R2 Storage
+ \ (Cloudflare)
+...
+provider> Cloudflare
+Option env_auth.
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
+Only applies if access_key_id and secret_access_key is blank.
+Choose a number from below, or type in your own boolean value (true or false).
+Press Enter for the default (false).
+ 1 / Enter AWS credentials in the next step.
+ \ (false)
+ 2 / Get AWS credentials from the environment (env vars or IAM).
+ \ (true)
+env_auth> 1
+Option access_key_id.
+AWS Access Key ID.
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+access_key_id> ACCESS_KEY
+Option secret_access_key.
+AWS Secret Access Key (password).
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+secret_access_key> SECRET_ACCESS_KEY
+Option region.
+Region to connect to.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / R2 buckets are automatically distributed across Cloudflare's data centers for low latency.
+ \ (auto)
+region> 1
+Option endpoint.
+Endpoint for S3 API.
+Required when using an S3 clone.
+Enter a value. Press Enter to leave empty.
+endpoint> https://ACCOUNT_ID.r2.cloudflarestorage.com
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n> n
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+This will leave your config looking something like:
+[r2]
+type = s3
+provider = Cloudflare
+access_key_id = ACCESS_KEY
+secret_access_key = SECRET_ACCESS_KEY
+region = auto
+endpoint = https://ACCOUNT_ID.r2.cloudflarestorage.com
+acl = private
+Now run rclone lsf r2: to see your buckets and rclone lsf r2:bucket to look within a bucket.
Dreamhost
Dreamhost DreamObjects is an object storage system based on CEPH.
To use rclone with Dreamhost, configure as above but leave the region blank and set the endpoint. You should end up with something like this in your config:
@@ -12254,15 +13594,135 @@ DigitalOcean Spaces
Once configured, you can create a new Space and begin copying files. For example:
rclone mkdir spaces:my-new-space
rclone copy /path/to/files spaces:my-new-space
-IBM COS (S3)
-Information stored with IBM Cloud Object Storage is encrypted and dispersed across multiple geographic locations, and accessed through an implementation of the S3 API. This service makes use of the distributed storage technologies provided by IBM’s Cloud Object Storage System (formerly Cleversafe). For more information visit: (http://www.ibm.com/cloud/object-storage)
-To configure access to IBM COS S3, follow the steps below:
-
-- Run rclone config and select n for a new remote.
-
- 2018/02/14 14:13:11 NOTICE: Config file "C:\\Users\\a\\.config\\rclone\\rclone.conf" not found - using defaults
- No remotes found, make a new one?
- n) New remote
+Huawei OBS
+Object Storage Service (OBS) provides stable, secure, efficient, and easy-to-use cloud storage that lets you store virtually any volume of unstructured data in any format and access it from anywhere.
+OBS provides an S3 interface, you can copy and modify the following configuration and add it to your rclone configuration file.
+[obs]
+type = s3
+provider = HuaweiOBS
+access_key_id = your-access-key-id
+secret_access_key = your-secret-access-key
+region = af-south-1
+endpoint = obs.af-south-1.myhuaweicloud.com
+acl = private
+Or you can also configure via the interactive command line:
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+name> obs
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+[snip]
+ 5 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi
+ \ (s3)
+[snip]
+Storage> 5
+Option provider.
+Choose your S3 provider.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+[snip]
+ 9 / Huawei Object Storage Service
+ \ (HuaweiOBS)
+[snip]
+provider> 9
+Option env_auth.
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
+Only applies if access_key_id and secret_access_key is blank.
+Choose a number from below, or type in your own boolean value (true or false).
+Press Enter for the default (false).
+ 1 / Enter AWS credentials in the next step.
+ \ (false)
+ 2 / Get AWS credentials from the environment (env vars or IAM).
+ \ (true)
+env_auth> 1
+Option access_key_id.
+AWS Access Key ID.
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+access_key_id> your-access-key-id
+Option secret_access_key.
+AWS Secret Access Key (password).
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+secret_access_key> your-secret-access-key
+Option region.
+Region to connect to.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / AF-Johannesburg
+ \ (af-south-1)
+ 2 / AP-Bangkok
+ \ (ap-southeast-2)
+[snip]
+region> 1
+Option endpoint.
+Endpoint for OBS API.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / AF-Johannesburg
+ \ (obs.af-south-1.myhuaweicloud.com)
+ 2 / AP-Bangkok
+ \ (obs.ap-southeast-2.myhuaweicloud.com)
+[snip]
+endpoint> 1
+Option acl.
+Canned ACL used when creating buckets and storing or copying objects.
+This ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.
+For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+Note that this ACL is applied when server-side copying objects as S3
+doesn't copy the ACL from the source but rather writes a fresh one.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ / Owner gets FULL_CONTROL.
+ 1 | No one else has access rights (default).
+ \ (private)
+[snip]
+acl> 1
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n>
+--------------------
+[obs]
+type = s3
+provider = HuaweiOBS
+access_key_id = your-access-key-id
+secret_access_key = your-secret-access-key
+region = af-south-1
+endpoint = obs.af-south-1.myhuaweicloud.com
+acl = private
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+Current remotes:
+
+Name Type
+==== ====
+obs s3
+
+e) Edit existing remote
+n) New remote
+d) Delete remote
+r) Rename remote
+c) Copy remote
+s) Set configuration password
+q) Quit config
+e/n/d/r/c/s/q> q
+IBM COS (S3)
+Information stored with IBM Cloud Object Storage is encrypted and dispersed across multiple geographic locations, and accessed through an implementation of the S3 API. This service makes use of the distributed storage technologies provided by IBM’s Cloud Object Storage System (formerly Cleversafe). For more information visit: (http://www.ibm.com/cloud/object-storage)
+To configure access to IBM COS S3, follow the steps below:
+
+- Run rclone config and select n for a new remote.
+
+ 2018/02/14 14:13:11 NOTICE: Config file "C:\\Users\\a\\.config\\rclone\\rclone.conf" not found - using defaults
+ No remotes found, make a new one?
+ n) New remote
s) Set configuration password
q) Quit config
n/s/q> n
@@ -12278,12 +13738,12 @@ IBM COS (S3)
\ "alias"
2 / Amazon Drive
\ "amazon cloud drive"
- 3 / Amazon S3 Complaint Storage Providers (Dreamhost, Ceph, Minio, IBM COS)
+ 3 / Amazon S3 Complaint Storage Providers (Dreamhost, Ceph, ChinaMobile, ArvanCloud, Minio, IBM COS)
\ "s3"
4 / Backblaze B2
\ "b2"
[snip]
- 23 / http Connection
+ 23 / HTTP
\ "http"
Storage> 3
@@ -12411,6 +13871,108 @@ IBM COS (S3)
rclone copy IBM-COS-XREGION:newbucket/file.txt .
6) Delete a file on remote.
rclone delete IBM-COS-XREGION:newbucket/file.txt
+IDrive e2
+Here is an example of making an IDrive e2 configuration. First run:
+rclone config
+This will guide you through an interactive setup process.
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+
+Enter name for new remote.
+name> e2
+
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+[snip]
+XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, IDrive e2, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS and Wasabi
+ \ (s3)
+[snip]
+Storage> s3
+
+Option provider.
+Choose your S3 provider.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+[snip]
+XX / IDrive e2
+ \ (IDrive)
+[snip]
+provider> IDrive
+
+Option env_auth.
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
+Only applies if access_key_id and secret_access_key is blank.
+Choose a number from below, or type in your own boolean value (true or false).
+Press Enter for the default (false).
+ 1 / Enter AWS credentials in the next step.
+ \ (false)
+ 2 / Get AWS credentials from the environment (env vars or IAM).
+ \ (true)
+env_auth>
+
+Option access_key_id.
+AWS Access Key ID.
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+access_key_id> YOUR_ACCESS_KEY
+
+Option secret_access_key.
+AWS Secret Access Key (password).
+Leave blank for anonymous access or runtime credentials.
+Enter a value. Press Enter to leave empty.
+secret_access_key> YOUR_SECRET_KEY
+
+Option acl.
+Canned ACL used when creating buckets and storing or copying objects.
+This ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.
+For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+Note that this ACL is applied when server-side copying objects as S3
+doesn't copy the ACL from the source but rather writes a fresh one.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ / Owner gets FULL_CONTROL.
+ 1 | No one else has access rights (default).
+ \ (private)
+ / Owner gets FULL_CONTROL.
+ 2 | The AllUsers group gets READ access.
+ \ (public-read)
+ / Owner gets FULL_CONTROL.
+ 3 | The AllUsers group gets READ and WRITE access.
+ | Granting this on a bucket is generally not recommended.
+ \ (public-read-write)
+ / Owner gets FULL_CONTROL.
+ 4 | The AuthenticatedUsers group gets READ access.
+ \ (authenticated-read)
+ / Object owner gets FULL_CONTROL.
+ 5 | Bucket owner gets READ access.
+ | If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
+ \ (bucket-owner-read)
+ / Both the object owner and the bucket owner get FULL_CONTROL over the object.
+ 6 | If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
+ \ (bucket-owner-full-control)
+acl>
+
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n>
+
+Configuration complete.
+Options:
+- type: s3
+- provider: IDrive
+- access_key_id: YOUR_ACCESS_KEY
+- secret_access_key: YOUR_SECRET_KEY
+- endpoint: q9d9.la12.idrivee2-5.com
+Keep this "e2" remote?
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
Minio
Minio is an object storage server built for cloud application developers and devops.
It is very easy to install and provides an S3 compatible server which can be used by rclone.
@@ -12485,6 +14047,7 @@ Scaleway
acl = private
server_side_encryption =
storage_class =
+C14 Cold Storage is the low-cost S3 Glacier alternative from Scaleway and it works the same way as on S3 by accepting the "GLACIER" storage_class. So you can configure your remote with the storage_class = GLACIER option to upload directly to C14. Don't forget that in this state you can't read files back after, you will need to restore them to "STANDARD" storage_class first before being able to read them (see "restore" section above)
Seagate Lyve Cloud
Seagate Lyve Cloud is an S3 compatible object storage platform from Seagate intended for enterprise use.
Here is a config run through for a remote called remote - you may choose a different name of course. Note that to create an access key and secret key you will need to create a service account first.
@@ -12499,7 +14062,7 @@ Seagate Lyve Cloud
Type of storage to configure.
Choose a number from below, or type in your own value.
[snip]
-XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS
+XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS
\ (s3)
[snip]
Storage> s3
@@ -12624,7 +14187,7 @@ Wasabi
Type of storage to configure.
Choose a number from below, or type in your own value
[snip]
-XX / Amazon S3 (also Dreamhost, Ceph, Minio)
+XX / Amazon S3 (also Dreamhost, Ceph, ChinaMobile, ArvanCloud, Minio)
\ "s3"
[snip]
Storage> s3
@@ -12726,7 +14289,7 @@ Alibaba OSS
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
[snip]
- 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Minio, and Tencent COS
+ 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Minio, and Tencent COS
\ "s3"
[snip]
Storage> s3
@@ -12814,83 +14377,420 @@ Alibaba OSS
e) Edit this remote
d) Delete this remote
y/e/d> y
-Tencent COS
-Tencent Cloud Object Storage (COS) is a distributed storage service offered by Tencent Cloud for unstructured data. It is secure, stable, massive, convenient, low-delay and low-cost.
-To configure access to Tencent COS, follow the steps below:
-
-- Run
rclone config and select n for a new remote.
-
-rclone config
-No remotes found, make a new one?
+China Mobile Ecloud Elastic Object Storage (EOS)
+Here is an example of making an China Mobile Ecloud Elastic Object Storage (EOS) configuration. First run:
+rclone config
+This will guide you through an interactive setup process.
+No remotes found, make a new one?
n) New remote
s) Set configuration password
q) Quit config
-n/s/q> n
-
-- Give the name of the configuration. For example, name it 'cos'.
-
-name> cos
-
-- Select
s3 storage.
-
-Choose a number from below, or type in your own value
-1 / 1Fichier
- \ "fichier"
- 2 / Alias for an existing remote
- \ "alias"
- 3 / Amazon Drive
- \ "amazon cloud drive"
- 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Minio, and Tencent COS
- \ "s3"
-[snip]
-Storage> s3
-
-- Select
TencentCOS provider.
-
-Choose a number from below, or type in your own value
-1 / Amazon Web Services (AWS) S3
- \ "AWS"
-[snip]
-11 / Tencent Cloud Object Storage (COS)
- \ "TencentCOS"
-[snip]
-provider> TencentCOS
-
-- Enter your SecretId and SecretKey of Tencent Cloud.
-
-Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
+n/s/q> n
+name> ChinaMobile
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+ ...
+ 5 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, RackCorp, SeaweedFS, and Tencent COS
+ \ (s3)
+ ...
+Storage> s3
+Option provider.
+Choose your S3 provider.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ ...
+ 4 / China Mobile Ecloud Elastic Object Storage (EOS)
+ \ (ChinaMobile)
+ ...
+provider> ChinaMobile
+Option env_auth.
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
Only applies if access_key_id and secret_access_key is blank.
-Enter a boolean value (true or false). Press Enter for the default ("false").
-Choose a number from below, or type in your own value
- 1 / Enter AWS credentials in the next step
- \ "false"
- 2 / Get AWS credentials from the environment (env vars or IAM)
- \ "true"
-env_auth> 1
+Choose a number from below, or type in your own boolean value (true or false).
+Press Enter for the default (false).
+ 1 / Enter AWS credentials in the next step.
+ \ (false)
+ 2 / Get AWS credentials from the environment (env vars or IAM).
+ \ (true)
+env_auth>
+Option access_key_id.
AWS Access Key ID.
Leave blank for anonymous access or runtime credentials.
-Enter a string value. Press Enter for the default ("").
-access_key_id> AKIDxxxxxxxxxx
-AWS Secret Access Key (password)
+Enter a value. Press Enter to leave empty.
+access_key_id> accesskeyid
+Option secret_access_key.
+AWS Secret Access Key (password).
Leave blank for anonymous access or runtime credentials.
-Enter a string value. Press Enter for the default ("").
-secret_access_key> xxxxxxxxxxx
-
-- Select endpoint for Tencent COS. This is the standard endpoint for different region.
-
- 1 / Beijing Region.
- \ "cos.ap-beijing.myqcloud.com"
- 2 / Nanjing Region.
- \ "cos.ap-nanjing.myqcloud.com"
- 3 / Shanghai Region.
- \ "cos.ap-shanghai.myqcloud.com"
- 4 / Guangzhou Region.
- \ "cos.ap-guangzhou.myqcloud.com"
-[snip]
-endpoint> 4
-
-- Choose acl and storage class.
-
+Enter a value. Press Enter to leave empty.
+secret_access_key> secretaccesskey
+Option endpoint.
+Endpoint for China Mobile Ecloud Elastic Object Storage (EOS) API.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ / The default endpoint - a good choice if you are unsure.
+ 1 | East China (Suzhou)
+ \ (eos-wuxi-1.cmecloud.cn)
+ 2 / East China (Jinan)
+ \ (eos-jinan-1.cmecloud.cn)
+ 3 / East China (Hangzhou)
+ \ (eos-ningbo-1.cmecloud.cn)
+ 4 / East China (Shanghai-1)
+ \ (eos-shanghai-1.cmecloud.cn)
+ 5 / Central China (Zhengzhou)
+ \ (eos-zhengzhou-1.cmecloud.cn)
+ 6 / Central China (Changsha-1)
+ \ (eos-hunan-1.cmecloud.cn)
+ 7 / Central China (Changsha-2)
+ \ (eos-zhuzhou-1.cmecloud.cn)
+ 8 / South China (Guangzhou-2)
+ \ (eos-guangzhou-1.cmecloud.cn)
+ 9 / South China (Guangzhou-3)
+ \ (eos-dongguan-1.cmecloud.cn)
+10 / North China (Beijing-1)
+ \ (eos-beijing-1.cmecloud.cn)
+11 / North China (Beijing-2)
+ \ (eos-beijing-2.cmecloud.cn)
+12 / North China (Beijing-3)
+ \ (eos-beijing-4.cmecloud.cn)
+13 / North China (Huhehaote)
+ \ (eos-huhehaote-1.cmecloud.cn)
+14 / Southwest China (Chengdu)
+ \ (eos-chengdu-1.cmecloud.cn)
+15 / Southwest China (Chongqing)
+ \ (eos-chongqing-1.cmecloud.cn)
+16 / Southwest China (Guiyang)
+ \ (eos-guiyang-1.cmecloud.cn)
+17 / Nouthwest China (Xian)
+ \ (eos-xian-1.cmecloud.cn)
+18 / Yunnan China (Kunming)
+ \ (eos-yunnan.cmecloud.cn)
+19 / Yunnan China (Kunming-2)
+ \ (eos-yunnan-2.cmecloud.cn)
+20 / Tianjin China (Tianjin)
+ \ (eos-tianjin-1.cmecloud.cn)
+21 / Jilin China (Changchun)
+ \ (eos-jilin-1.cmecloud.cn)
+22 / Hubei China (Xiangyan)
+ \ (eos-hubei-1.cmecloud.cn)
+23 / Jiangxi China (Nanchang)
+ \ (eos-jiangxi-1.cmecloud.cn)
+24 / Gansu China (Lanzhou)
+ \ (eos-gansu-1.cmecloud.cn)
+25 / Shanxi China (Taiyuan)
+ \ (eos-shanxi-1.cmecloud.cn)
+26 / Liaoning China (Shenyang)
+ \ (eos-liaoning-1.cmecloud.cn)
+27 / Hebei China (Shijiazhuang)
+ \ (eos-hebei-1.cmecloud.cn)
+28 / Fujian China (Xiamen)
+ \ (eos-fujian-1.cmecloud.cn)
+29 / Guangxi China (Nanning)
+ \ (eos-guangxi-1.cmecloud.cn)
+30 / Anhui China (Huainan)
+ \ (eos-anhui-1.cmecloud.cn)
+endpoint> 1
+Option location_constraint.
+Location constraint - must match endpoint.
+Used when creating buckets only.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / East China (Suzhou)
+ \ (wuxi1)
+ 2 / East China (Jinan)
+ \ (jinan1)
+ 3 / East China (Hangzhou)
+ \ (ningbo1)
+ 4 / East China (Shanghai-1)
+ \ (shanghai1)
+ 5 / Central China (Zhengzhou)
+ \ (zhengzhou1)
+ 6 / Central China (Changsha-1)
+ \ (hunan1)
+ 7 / Central China (Changsha-2)
+ \ (zhuzhou1)
+ 8 / South China (Guangzhou-2)
+ \ (guangzhou1)
+ 9 / South China (Guangzhou-3)
+ \ (dongguan1)
+10 / North China (Beijing-1)
+ \ (beijing1)
+11 / North China (Beijing-2)
+ \ (beijing2)
+12 / North China (Beijing-3)
+ \ (beijing4)
+13 / North China (Huhehaote)
+ \ (huhehaote1)
+14 / Southwest China (Chengdu)
+ \ (chengdu1)
+15 / Southwest China (Chongqing)
+ \ (chongqing1)
+16 / Southwest China (Guiyang)
+ \ (guiyang1)
+17 / Nouthwest China (Xian)
+ \ (xian1)
+18 / Yunnan China (Kunming)
+ \ (yunnan)
+19 / Yunnan China (Kunming-2)
+ \ (yunnan2)
+20 / Tianjin China (Tianjin)
+ \ (tianjin1)
+21 / Jilin China (Changchun)
+ \ (jilin1)
+22 / Hubei China (Xiangyan)
+ \ (hubei1)
+23 / Jiangxi China (Nanchang)
+ \ (jiangxi1)
+24 / Gansu China (Lanzhou)
+ \ (gansu1)
+25 / Shanxi China (Taiyuan)
+ \ (shanxi1)
+26 / Liaoning China (Shenyang)
+ \ (liaoning1)
+27 / Hebei China (Shijiazhuang)
+ \ (hebei1)
+28 / Fujian China (Xiamen)
+ \ (fujian1)
+29 / Guangxi China (Nanning)
+ \ (guangxi1)
+30 / Anhui China (Huainan)
+ \ (anhui1)
+location_constraint> 1
+Option acl.
+Canned ACL used when creating buckets and storing or copying objects.
+This ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.
+For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+Note that this ACL is applied when server-side copying objects as S3
+doesn't copy the ACL from the source but rather writes a fresh one.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ / Owner gets FULL_CONTROL.
+ 1 | No one else has access rights (default).
+ \ (private)
+ / Owner gets FULL_CONTROL.
+ 2 | The AllUsers group gets READ access.
+ \ (public-read)
+ / Owner gets FULL_CONTROL.
+ 3 | The AllUsers group gets READ and WRITE access.
+ | Granting this on a bucket is generally not recommended.
+ \ (public-read-write)
+ / Owner gets FULL_CONTROL.
+ 4 | The AuthenticatedUsers group gets READ access.
+ \ (authenticated-read)
+ / Object owner gets FULL_CONTROL.
+acl> private
+Option server_side_encryption.
+The server-side encryption algorithm used when storing this object in S3.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / None
+ \ ()
+ 2 / AES256
+ \ (AES256)
+server_side_encryption>
+Option storage_class.
+The storage class to use when storing new objects in ChinaMobile.
+Choose a number from below, or type in your own value.
+Press Enter to leave empty.
+ 1 / Default
+ \ ()
+ 2 / Standard storage class
+ \ (STANDARD)
+ 3 / Archive storage mode
+ \ (GLACIER)
+ 4 / Infrequent access storage mode
+ \ (STANDARD_IA)
+storage_class>
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n> n
+--------------------
+[ChinaMobile]
+type = s3
+provider = ChinaMobile
+access_key_id = accesskeyid
+secret_access_key = secretaccesskey
+endpoint = eos-wuxi-1.cmecloud.cn
+location_constraint = wuxi1
+acl = private
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+ArvanCloud
+ArvanCloud ArvanCloud Object Storage goes beyond the limited traditional file storage. It gives you access to backup and archived files and allows sharing. Files like profile image in the app, images sent by users or scanned documents can be stored securely and easily in our Object Storage service.
+ArvanCloud provides an S3 interface which can be configured for use with rclone like this.
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+n/s> n
+name> ArvanCloud
+Type of storage to configure.
+Choose a number from below, or type in your own value
+[snip]
+XX / Amazon S3 (also Dreamhost, Ceph, ChinaMobile, ArvanCloud, Minio)
+ \ "s3"
+[snip]
+Storage> s3
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars). Only applies if access_key_id and secret_access_key is blank.
+Choose a number from below, or type in your own value
+ 1 / Enter AWS credentials in the next step
+ \ "false"
+ 2 / Get AWS credentials from the environment (env vars or IAM)
+ \ "true"
+env_auth> 1
+AWS Access Key ID - leave blank for anonymous access or runtime credentials.
+access_key_id> YOURACCESSKEY
+AWS Secret Access Key (password) - leave blank for anonymous access or runtime credentials.
+secret_access_key> YOURSECRETACCESSKEY
+Region to connect to.
+Choose a number from below, or type in your own value
+ / The default endpoint - a good choice if you are unsure.
+ 1 | US Region, Northern Virginia, or Pacific Northwest.
+ | Leave location constraint empty.
+ \ "us-east-1"
+[snip]
+region>
+Endpoint for S3 API.
+Leave blank if using ArvanCloud to use the default endpoint for the region.
+Specify if using an S3 clone such as Ceph.
+endpoint> s3.arvanstorage.com
+Location constraint - must be set to match the Region. Used when creating buckets only.
+Choose a number from below, or type in your own value
+ 1 / Empty for Iran-Tehran Region.
+ \ ""
+[snip]
+location_constraint>
+Canned ACL used when creating buckets and/or storing objects in S3.
+For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+Choose a number from below, or type in your own value
+ 1 / Owner gets FULL_CONTROL. No one else has access rights (default).
+ \ "private"
+[snip]
+acl>
+The server-side encryption algorithm used when storing this object in S3.
+Choose a number from below, or type in your own value
+ 1 / None
+ \ ""
+ 2 / AES256
+ \ "AES256"
+server_side_encryption>
+The storage class to use when storing objects in S3.
+Choose a number from below, or type in your own value
+ 1 / Default
+ \ ""
+ 2 / Standard storage class
+ \ "STANDARD"
+storage_class>
+Remote config
+--------------------
+[ArvanCloud]
+env_auth = false
+access_key_id = YOURACCESSKEY
+secret_access_key = YOURSECRETACCESSKEY
+region = ir-thr-at1
+endpoint = s3.arvanstorage.com
+location_constraint =
+acl =
+server_side_encryption =
+storage_class =
+--------------------
+y) Yes this is OK
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+This will leave the config file looking like this.
+[ArvanCloud]
+type = s3
+provider = ArvanCloud
+env_auth = false
+access_key_id = YOURACCESSKEY
+secret_access_key = YOURSECRETACCESSKEY
+region =
+endpoint = s3.arvanstorage.com
+location_constraint =
+acl =
+server_side_encryption =
+storage_class =
+Tencent COS
+Tencent Cloud Object Storage (COS) is a distributed storage service offered by Tencent Cloud for unstructured data. It is secure, stable, massive, convenient, low-delay and low-cost.
+To configure access to Tencent COS, follow the steps below:
+
+- Run
rclone config and select n for a new remote.
+
+rclone config
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+
+- Give the name of the configuration. For example, name it 'cos'.
+
+name> cos
+
+- Select
s3 storage.
+
+Choose a number from below, or type in your own value
+1 / 1Fichier
+ \ "fichier"
+ 2 / Alias for an existing remote
+ \ "alias"
+ 3 / Amazon Drive
+ \ "amazon cloud drive"
+ 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, ChinaMobile, ArvanCloud, Digital Ocean, Dreamhost, Huawei OBS, IBM COS, Minio, and Tencent COS
+ \ "s3"
+[snip]
+Storage> s3
+
+- Select
TencentCOS provider.
+
+Choose a number from below, or type in your own value
+1 / Amazon Web Services (AWS) S3
+ \ "AWS"
+[snip]
+11 / Tencent Cloud Object Storage (COS)
+ \ "TencentCOS"
+[snip]
+provider> TencentCOS
+
+- Enter your SecretId and SecretKey of Tencent Cloud.
+
+Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
+Only applies if access_key_id and secret_access_key is blank.
+Enter a boolean value (true or false). Press Enter for the default ("false").
+Choose a number from below, or type in your own value
+ 1 / Enter AWS credentials in the next step
+ \ "false"
+ 2 / Get AWS credentials from the environment (env vars or IAM)
+ \ "true"
+env_auth> 1
+AWS Access Key ID.
+Leave blank for anonymous access or runtime credentials.
+Enter a string value. Press Enter for the default ("").
+access_key_id> AKIDxxxxxxxxxx
+AWS Secret Access Key (password)
+Leave blank for anonymous access or runtime credentials.
+Enter a string value. Press Enter for the default ("").
+secret_access_key> xxxxxxxxxxx
+
+- Select endpoint for Tencent COS. This is the standard endpoint for different region.
+
+ 1 / Beijing Region.
+ \ "cos.ap-beijing.myqcloud.com"
+ 2 / Nanjing Region.
+ \ "cos.ap-nanjing.myqcloud.com"
+ 3 / Shanghai Region.
+ \ "cos.ap-shanghai.myqcloud.com"
+ 4 / Guangzhou Region.
+ \ "cos.ap-guangzhou.myqcloud.com"
+[snip]
+endpoint> 4
+
+- Choose acl and storage class.
+
Note that this ACL is applied when server-side copying objects as S3
doesn't copy the ACL from the source but rather writes a fresh one.
Enter a string value. Press Enter for the default ("").
@@ -13001,7 +14901,7 @@ Comparison with the native protocol
For more detailed comparison please check the documentation of the storj backend.
Limitations
rclone about is not supported by the S3 backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Backblaze B2
B2 is Backblaze's cloud storage system.
Paths are specified as remote:bucket (or remote: for the lsd command.) You may put subdirectories in too, e.g. remote:bucket/path/to/dir.
@@ -13089,6 +14989,7 @@ Transfers
Versions
When rclone uploads a new version of a file it creates a new version of it. Likewise when you delete a file, the old version will be marked hidden and still be available. Conversely, you may opt in to a "hard delete" of files with the --b2-hard-delete flag which would permanently remove the file instead of hiding it.
Old versions of files, where available, are visible using the --b2-versions flag.
+It is also possible to view a bucket as it was at a certain point in time, using the --b2-version-at flag. This will show the file versions as they were at that time, showing files that have been deleted afterwards, and hiding files that were created since.
If you wish to remove all the old versions then you can use the rclone cleanup remote:bucket command which will delete all the old versions of files, leaving the current ones intact. You can also supply a path and only old versions under that path will be deleted, e.g. rclone cleanup remote:bucket/path/to/stuff.
Note that cleanup will remove partially uploaded files from the bucket if they are more than a day old.
When you purge a bucket, the current and the old versions will be deleted then the bucket will be deleted.
@@ -13159,7 +15060,7 @@ B2 and rclone link
https://f002.backblazeb2.com/file/bucket/path/folder/file3?Authorization=xxxxxxxx
Standard options
-Here are the standard options specific to b2 (Backblaze B2).
+Here are the Standard options specific to b2 (Backblaze B2).
--b2-account
Account ID or Application Key ID.
Properties:
@@ -13188,7 +15089,7 @@ --b2-hard-delete
- Default: false
Advanced options
-Here are the advanced options specific to b2 (Backblaze B2).
+Here are the Advanced options specific to b2 (Backblaze B2).
--b2-endpoint
Endpoint for the service.
Leave blank normally.
@@ -13225,6 +15126,16 @@ --b2-versions
- Type: bool
- Default: false
+--b2-version-at
+Show file versions as they were at the specified time.
+Note that when using this no file write operations are permitted, so you can't upload files or delete them.
+Properties:
+
+- Config: version_at
+- Env Var: RCLONE_B2_VERSION_AT
+- Type: Time
+- Default: off
+
--b2-upload-cutoff
Cutoff for switching to chunked upload.
Files above this size will be uploaded in chunks of "--b2-chunk-size".
@@ -13321,7 +15232,7 @@ --b2-encoding
Limitations
rclone about is not supported by the B2 backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Box
Paths are specified as remote:path
Paths may be as deep as required, e.g. remote:directory/subdirectory.
@@ -13519,7 +15430,7 @@ Root folder ID
In order to do this you will have to find the Folder ID of the directory you wish rclone to display. This will be the last segment of the URL when you open the relevant folder in the Box web interface.
So if the folder you want rclone to use has a URL which looks like https://app.box.com/folder/11xxxxxxxxx8 in the browser, then you use 11xxxxxxxxx8 as the root_folder_id in the config.
Standard options
-Here are the standard options specific to box (Box).
+Here are the Standard options specific to box (Box).
--box-client-id
OAuth Client Id.
Leave blank normally.
@@ -13581,7 +15492,7 @@ --box-box-sub-type
Advanced options
-Here are the advanced options specific to box (Box).
+Here are the Advanced options specific to box (Box).
--box-token
OAuth Access Token as a JSON blob.
Properties:
@@ -13671,7 +15582,7 @@ Limitations
Box file names can't have the \ character in. rclone maps this to and from an identical looking unicode equivalent \ (U+FF3C Fullwidth Reverse Solidus).
Box only supports filenames up to 255 characters in length.
rclone about is not supported by the Box backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Cache (DEPRECATED)
The cache remote wraps another existing remote and stores file structure and its data for long running tasks like rclone mount.
Status
@@ -13833,7 +15744,7 @@ rc cache/expire
Purge a remote from the cache backend. Supports either a directory or a file. It supports both encrypted and unencrypted file names if cache is wrapped by crypt.
Params: - remote = path to remote (required) - withData = true/false to delete cached data (chunks) as well (optional, false by default)
Standard options
-Here are the standard options specific to cache (Cache a remote).
+Here are the Standard options specific to cache (Cache a remote).
--cache-remote
Remote to cache.
Normally should contain a ':' and a path, e.g. "myremote:path/to/dir", "myremote:bucket" or maybe "myremote:" (not recommended).
@@ -13947,7 +15858,7 @@ --cache-chunk-total-size
Advanced options
-Here are the advanced options specific to cache (Cache a remote).
+Here are the Advanced options specific to cache (Cache a remote).
--cache-plex-token
The plex token for authentication - auto set normally.
Properties:
@@ -14101,7 +16012,7 @@ Backend commands
Run them with
rclone backend COMMAND remote:
The help below will explain what arguments each command takes.
-See the "rclone backend" command for more info on how to pass options and arguments.
+See the backend command for more info on how to pass options and arguments.
These can be run on a running backend using the rc command backend/command.
stats
Print stats on the cache backend in JSON format.
@@ -14180,7 +16091,7 @@ Chunk names
For example, if name format is big_*-##.part and original file name is data.txt and numbering starts from 0, then the first chunk will be named big_data.txt-00.part, the 99th chunk will be big_data.txt-98.part and the 302nd chunk will become big_data.txt-301.part.
Note that list assembles composite directory entries only when chunk names match the configured format and treats non-conforming file names as normal non-chunked files.
When using norename transactions, chunk names will additionally have a unique file version suffix. For example, BIG_FILE_NAME.rclone_chunk.001_bp562k.
-
+
Besides data chunks chunker will by default create metadata object for a composite file. The object is named after the original file. Chunker allows user to disable metadata completely (the none format). Note that metadata is normally not created for files smaller than the configured chunk size. This may change in future rclone releases.
This is the default format. It supports hash sums and chunk validation for composite files. Meta objects carry the following fields:
@@ -14221,7 +16132,7 @@ Caveats and Limitations
Chunker included in rclone releases up to v1.54 can sometimes fail to detect metadata produced by recent versions of rclone. We recommend users to keep rclone up-to-date to avoid data corruption.
Changing transactions is dangerous and requires explicit migration.
Standard options
-Here are the standard options specific to chunker (Transparently chunk/split large files).
+Here are the Standard options specific to chunker (Transparently chunk/split large files).
--chunker-remote
Remote to chunk/unchunk.
Normally should contain a ':' and a path, e.g. "myremote:path/to/dir", "myremote:bucket" or maybe "myremote:" (not recommended).
@@ -14285,7 +16196,7 @@ --chunker-hash-type
Advanced options
-Here are the advanced options specific to chunker (Transparently chunk/split large files).
+Here are the Advanced options specific to chunker (Transparently chunk/split large files).
String format of chunk file names.
The two placeholders are: base file name (*) and chunk number (#...). There must be one and only one asterisk and one or more consecutive hash characters. If chunk number has less digits than the number of hashes, it is left-padded by zeros. If there are more digits in the number, they are left as is. Possible chunk files are ignored if their name does not match given format.
@@ -14536,7 +16447,7 @@ Restricted filename characters
Invalid UTF-8 bytes will also be replaced, as they can't be used in JSON strings.
Standard options
-Here are the standard options specific to sharefile (Citrix Sharefile).
+Here are the Standard options specific to sharefile (Citrix Sharefile).
--sharefile-root-folder-id
ID of the root folder.
Leave blank to access "Personal Folders". You can use one of the standard values here or any folder ID (long hex number ID).
@@ -14571,7 +16482,7 @@ --sharefile-root-folder-id
Advanced options
-Here are the advanced options specific to sharefile (Citrix Sharefile).
+Here are the Advanced options specific to sharefile (Citrix Sharefile).
--sharefile-upload-cutoff
Cutoff for switching to multipart upload.
Properties:
@@ -14617,7 +16528,7 @@ Limitations
Note that ShareFile is case insensitive so you can't have a file called "Hello.doc" and one called "hello.doc".
ShareFile only supports filenames up to 256 characters in length.
rclone about is not supported by the Citrix ShareFile backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Crypt
Rclone crypt remotes encrypt and decrypt other remotes.
A remote of type crypt does not access a storage system directly, but instead wraps another remote, which in turn accesses the storage system. This is similar to how alias, union, chunker and a few others work. It makes the usage very flexible, as you can add a layer, in this case an encryption layer, on top of any other backend, even in multiple layers. Rclone's functionality can be used as with any other remote, for example you can mount a crypt remote.
@@ -14816,7 +16727,7 @@ Modified time and hashes
Hashes are not stored for crypt. However the data integrity is protected by an extremely strong crypto authenticator.
Use the rclone cryptcheck command to check the integrity of a crypted remote instead of rclone check which can't check the checksums properly.
Standard options
-Here are the standard options specific to crypt (Encrypt/Decrypt a remote).
+Here are the Standard options specific to crypt (Encrypt/Decrypt a remote).
--crypt-remote
Remote to encrypt/decrypt.
Normally should contain a ':' and a path, e.g. "myremote:path/to/dir", "myremote:bucket" or maybe "myremote:" (not recommended).
@@ -14896,7 +16807,7 @@ --crypt-password2
Required: false
Advanced options
-Here are the advanced options specific to crypt (Encrypt/Decrypt a remote).
+Here are the Advanced options specific to crypt (Encrypt/Decrypt a remote).
--crypt-server-side-across-configs
Allow server-side operations (e.g. copy) to work across different crypt configs.
Normally this option is not what you want, but if you have two crypts pointing to the same backend you can use it.
@@ -14965,12 +16876,15 @@ --crypt-filename-encoding
+
+Any metadata supported by the underlying remote is read and written.
+See the metadata docs for more info.
Backend commands
Here are the commands specific to the crypt backend.
Run them with
rclone backend COMMAND remote:
The help below will explain what arguments each command takes.
-See the "rclone backend" command for more info on how to pass options and arguments.
+See the backend command for more info on how to pass options and arguments.
These can be run on a running backend using the rc command backend/command.
encode
Encode the given filename(s)
@@ -15050,7 +16964,7 @@ Name encryption
Key derivation
Rclone uses scrypt with parameters N=16384, r=8, p=1 with an optional user supplied salt (password2) to derive the 32+32+16 = 80 bytes of key material required. If the user doesn't supply a salt then rclone uses an internal one.
scrypt makes it impractical to mount a dictionary attack on rclone encrypted data. For full protection against this you should always use a salt.
-SEE ALSO
+SEE ALSO
@@ -15114,7 +17028,7 @@ File types
File names
The compressed files will be named *.###########.gz where * is the base file and the # part is base64 encoded size of the uncompressed file. The file names should not be changed by anything other than the rclone compression backend.
Standard options
-Here are the standard options specific to compress (Compress a remote).
+Here are the Standard options specific to compress (Compress a remote).
--compress-remote
Remote to compress.
Properties:
@@ -15141,7 +17055,7 @@ --compress-mode
Advanced options
-Here are the advanced options specific to compress (Compress a remote).
+Here are the Advanced options specific to compress (Compress a remote).
--compress-level
GZIP compression level (-2 to 9).
Generally -1 (default, equivalent to 5) is recommended. Levels 1 to 9 increase compression at the cost of speed. Going past 6 generally offers very little return.
@@ -15163,41 +17077,142 @@ --compress-ram-cache-limit
Type: SizeSuffix
Default: 20Mi
-Dropbox
-Paths are specified as remote:path
-Dropbox paths may be as deep as required, e.g. remote:directory/subdirectory.
+
+Any metadata supported by the underlying remote is read and written.
+See the metadata docs for more info.
+Combine
+The combine backend joins remotes together into a single directory tree.
+For example you might have a remote for images on one provider:
+$ rclone tree s3:imagesbucket
+/
+├── image1.jpg
+└── image2.jpg
+And a remote for files on another:
+$ rclone tree drive:important/files
+/
+├── file1.txt
+└── file2.txt
+The combine backend can join these together into a synthetic directory structure like this:
+$ rclone tree combined:
+/
+├── files
+│ ├── file1.txt
+│ └── file2.txt
+└── images
+ ├── image1.jpg
+ └── image2.jpg
+You'd do this by specifying an upstreams parameter in the config like this
+upstreams = images=s3:imagesbucket files=drive:important/files
+During the initial setup with rclone config you will specify the upstreams remotes as a space separated list. The upstream remotes can either be a local paths or other remotes.
Configuration
-The initial setup for dropbox involves getting a token from Dropbox which you need to do in your browser. rclone config walks you through it.
-Here is an example of how to make a remote called remote. First run:
+Here is an example of how to make a combine called remote for the example above. First run:
rclone config
This will guide you through an interactive setup process:
-n) New remote
-d) Delete remote
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
q) Quit config
-e/n/d/q> n
+n/s/q> n
name> remote
+Option Storage.
Type of storage to configure.
-Choose a number from below, or type in your own value
-[snip]
-XX / Dropbox
- \ "dropbox"
-[snip]
-Storage> dropbox
-Dropbox App Key - leave blank normally.
-app_key>
-Dropbox App Secret - leave blank normally.
-app_secret>
-Remote config
-Please visit:
-https://www.dropbox.com/1/oauth2/authorize?client_id=XXXXXXXXXXXXXXX&response_type=code
-Enter the code: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXX
+Choose a number from below, or type in your own value.
+...
+XX / Combine several remotes into one
+ \ (combine)
+...
+Storage> combine
+Option upstreams.
+Upstreams for combining
+These should be in the form
+ dir=remote:path dir2=remote2:path
+Where before the = is specified the root directory and after is the remote to
+put there.
+Embedded spaces can be added using quotes
+ "dir=remote:path with space" "dir2=remote2:path with space"
+Enter a fs.SpaceSepList value.
+upstreams> images=s3:imagesbucket files=drive:important/files
--------------------
[remote]
-app_key =
-app_secret =
-token = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+type = combine
+upstreams = images=s3:imagesbucket files=drive:important/files
--------------------
-y) Yes this is OK
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+Configuring for Google Drive Shared Drives
+Rclone has a convenience feature for making a combine backend for all the shared drives you have access to.
+Assuming your main (non shared drive) Google drive remote is called drive: you would run
+rclone backend -o config drives drive:
+This would produce something like this:
+[My Drive]
+type = alias
+remote = drive,team_drive=0ABCDEF-01234567890,root_folder_id=:
+
+[Test Drive]
+type = alias
+remote = drive,team_drive=0ABCDEFabcdefghijkl,root_folder_id=:
+
+[AllDrives]
+type = combine
+remote = "My Drive=My Drive:" "Test Drive=Test Drive:"
+If you then add that config to your config file (find it with rclone config file) then you can access all the shared drives in one place with the AllDrives: remote.
+See the Google Drive docs for full info.
+Standard options
+Here are the Standard options specific to combine (Combine several remotes into one).
+--combine-upstreams
+Upstreams for combining
+These should be in the form
+dir=remote:path dir2=remote2:path
+Where before the = is specified the root directory and after is the remote to put there.
+Embedded spaces can be added using quotes
+"dir=remote:path with space" "dir2=remote2:path with space"
+Properties:
+
+- Config: upstreams
+- Env Var: RCLONE_COMBINE_UPSTREAMS
+- Type: SpaceSepList
+- Default:
+
+
+Any metadata supported by the underlying remote is read and written.
+See the metadata docs for more info.
+Dropbox
+Paths are specified as remote:path
+Dropbox paths may be as deep as required, e.g. remote:directory/subdirectory.
+Configuration
+The initial setup for dropbox involves getting a token from Dropbox which you need to do in your browser. rclone config walks you through it.
+Here is an example of how to make a remote called remote. First run:
+ rclone config
+This will guide you through an interactive setup process:
+n) New remote
+d) Delete remote
+q) Quit config
+e/n/d/q> n
+name> remote
+Type of storage to configure.
+Choose a number from below, or type in your own value
+[snip]
+XX / Dropbox
+ \ "dropbox"
+[snip]
+Storage> dropbox
+Dropbox App Key - leave blank normally.
+app_key>
+Dropbox App Secret - leave blank normally.
+app_secret>
+Remote config
+Please visit:
+https://www.dropbox.com/1/oauth2/authorize?client_id=XXXXXXXXXXXXXXX&response_type=code
+Enter the code: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXX
+--------------------
+[remote]
+app_key =
+app_secret =
+token = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+--------------------
+y) Yes this is OK
e) Edit this remote
d) Delete this remote
y/e/d> y
@@ -15287,8 +17302,8 @@ --dropbox-batch-mode async
This provides the maximum possible upload speed especially with lots of small files, however rclone can't check the file got uploaded properly using this mode.
If you are using this mode then using "rclone check" after the transfer completes is recommended. Or you could do an initial transfer with --dropbox-batch-mode async then do a final transfer with --dropbox-batch-mode sync (the default).
Note that there may be a pause when quitting rclone while rclone finishes up the last batch using this mode.
-Standard options
-Here are the standard options specific to dropbox (Dropbox).
+Standard options
+Here are the Standard options specific to dropbox (Dropbox).
--dropbox-client-id
OAuth Client Id.
Leave blank normally.
@@ -15310,7 +17325,7 @@ --dropbox-client-secret
Required: false
Advanced options
-Here are the advanced options specific to dropbox (Dropbox).
+Here are the Advanced options specific to dropbox (Dropbox).
--dropbox-token
OAuth Access Token as a JSON blob.
Properties:
@@ -15476,7 +17491,7 @@ Get your own Dropbox App ID
Enterprise File Fabric
This backend supports Storage Made Easy's Enterprise File Fabric™ which provides a software solution to integrate and unify File and Object Storage accessible through a global file system.
-Configuration
+Configuration
The initial setup for the Enterprise File Fabric backend involves getting a token from the the Enterprise File Fabric which you need to do in your browser. rclone config walks you through it.
Here is an example of how to make a remote called remote. First run:
rclone config
@@ -15570,8 +17585,8 @@ Root folder ID
120673757,My contacts/
120673761,S3 Storage/
The ID for "S3 Storage" would be 120673761.
-Standard options
-Here are the standard options specific to filefabric (Enterprise File Fabric).
+Standard options
+Here are the Standard options specific to filefabric (Enterprise File Fabric).
--filefabric-url
URL of the Enterprise File Fabric to connect to.
Properties:
@@ -15620,7 +17635,7 @@ --filefabric-permanent-token
Required: false
Advanced options
-Here are the advanced options specific to filefabric (Enterprise File Fabric).
+Here are the Advanced options specific to filefabric (Enterprise File Fabric).
--filefabric-token
Session Token.
This is a session token which rclone caches in the config file. It is usually valid for 1 hour.
@@ -15666,7 +17681,7 @@ FTP
FTP is the File Transfer Protocol. Rclone FTP support is provided using the github.com/jlaffaye/ftp package.
Limitations of Rclone's FTP backend
Paths are specified as remote:path. If the path does not begin with a / it is relative to the home directory of the user. An empty path remote: refers to the user's home directory.
-Configuration
+Configuration
To create an FTP configuration named remote, run
rclone config
Rclone config guides you through an interactive setup process. A minimal rclone FTP remote definition only requires host, username and password. For an anonymous FTP server, use anonymous as username and your email address as password.
@@ -15682,7 +17697,7 @@ Configuration
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
[snip]
-XX / FTP Connection
+XX / FTP
\ "ftp"
[snip]
Storage> ftp
@@ -15776,8 +17791,8 @@ Restricted filename characters
This backend's interactive configuration wizard provides a selection of sensible encoding settings for major FTP servers: ProFTPd, PureFTPd, VsFTPd. Just hit a selection number when prompted.
-Standard options
-Here are the standard options specific to ftp (FTP Connection).
+Standard options
+Here are the Standard options specific to ftp (FTP).
--ftp-host
FTP host to connect to.
E.g. "ftp.example.com".
@@ -15837,7 +17852,7 @@ --ftp-explicit-tls
Default: false
Advanced options
-Here are the advanced options specific to ftp (FTP Connection).
+Here are the Advanced options specific to ftp (FTP).
--ftp-concurrency
Maximum number of FTP simultaneous connections, 0 for unlimited.
Properties:
@@ -15874,6 +17889,15 @@ --ftp-disable-mlsd
Type: bool
Default: false
+--ftp-disable-utf8
+Disable using UTF-8 even if server advertises support.
+Properties:
+
+- Config: disable_utf8
+- Env Var: RCLONE_FTP_DISABLE_UTF8
+- Type: bool
+- Default: false
+
--ftp-writing-mdtm
Use MDTM to set modification time (VsFtpd quirk)
Properties:
@@ -15970,7 +17994,7 @@ Limitations
FTP servers acting as rclone remotes must support passive mode. The mode cannot be configured as passive is the only supported one. Rclone's FTP implementation is not compatible with active mode as the library it uses doesn't support it. This will likely never be supported due to security concerns.
Rclone's FTP backend does not support any checksums but can compare file sizes.
rclone about is not supported by the FTP backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
The implementation of : --dump headers, --dump bodies, --dump auth for debugging isn't the same as for rclone HTTP based backends - it has less fine grained control.
--timeout isn't supported (but --contimeout is).
--bind isn't supported.
@@ -15982,7 +18006,7 @@ Modified time
You can use the following command to check whether rclone can use precise time with your FTP server: rclone backend features your_ftp_remote: (the trailing colon is important). Look for the number in the line tagged by Precision designating the remote time precision expressed as nanoseconds. A value of 1000000000 means that file time precision of 1 second is available. A value of 3153600000000000000 (or another large number) means "unsupported".
Google Cloud Storage
Paths are specified as remote:bucket (or remote: for the lsd command.) You may put subdirectories in too, e.g. remote:bucket/path/to/dir.
-Configuration
+Configuration
The initial setup for google cloud storage involves getting a token from Google Cloud Storage which you need to do in your browser. rclone config walks you through it.
Here is an example of how to make a remote called remote. First run:
rclone config
@@ -16177,8 +18201,8 @@ Restricted filename characters
Invalid UTF-8 bytes will also be replaced, as they can't be used in JSON strings.
-Standard options
-Here are the standard options specific to google cloud storage (Google Cloud Storage (this is not Google Drive)).
+Standard options
+Here are the Standard options specific to google cloud storage (Google Cloud Storage (this is not Google Drive)).
--gcs-client-id
OAuth Client Id.
Leave blank normally.
@@ -16532,7 +18556,7 @@ --gcs-storage-class
Advanced options
-Here are the advanced options specific to google cloud storage (Google Cloud Storage (this is not Google Drive)).
+Here are the Advanced options specific to google cloud storage (Google Cloud Storage (this is not Google Drive)).
--gcs-token
OAuth Access Token as a JSON blob.
Properties:
@@ -16562,6 +18586,27 @@ --gcs-token-url
Type: string
Required: false
+--gcs-no-check-bucket
+If set, don't attempt to check the bucket exists or create it.
+This can be useful when trying to minimise the number of transactions rclone does if you know the bucket exists already.
+Properties:
+
+- Config: no_check_bucket
+- Env Var: RCLONE_GCS_NO_CHECK_BUCKET
+- Type: bool
+- Default: false
+
+--gcs-decompress
+If set this will decompress gzip encoded objects.
+It is possible to upload objects to GCS with "Content-Encoding: gzip" set. Normally rclone will download these files files as compressed objects.
+If this flag is set then rclone will decompress these files with "Content-Encoding: gzip" as they are received. This means that rclone can't check the size and hash but the file contents will be decompressed.
+Properties:
+
+- Config: decompress
+- Env Var: RCLONE_GCS_DECOMPRESS
+- Type: bool
+- Default: false
+
--gcs-encoding
The encoding for the backend.
See the encoding section in the overview for more info.
@@ -16574,11 +18619,11 @@ --gcs-encoding
Limitations
rclone about is not supported by the Google Cloud Storage backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Google Drive
Paths are specified as drive:path
Drive paths may be as deep as required, e.g. drive:directory/subdirectory.
-Configuration
+Configuration
The initial setup for drive involves getting a token from Google drive which you need to do in your browser. rclone config walks you through it.
Here is an example of how to make a remote called remote. First run:
rclone config
@@ -16619,8 +18664,6 @@ Configuration
5 | does not allow any access to read or download file content.
\ "drive.metadata.readonly"
scope> 1
-ID of the root folder - leave blank normally. Fill in to access "Computers" folders. (see docs).
-root_folder_id>
Service Account Credentials JSON file path - needed only if you want use SA instead of interactive login.
service_account_file>
Remote config
@@ -16677,7 +18720,7 @@ drive.appfolder
This allows read only access to file names only. It does not allow rclone to download or upload data, or rename or delete files or directories.
Root folder ID
-You can set the root_folder_id for rclone. This is the directory (identified by its Folder ID) that rclone considers to be the root of your drive.
+This option has been moved to the advanced section. You can set the root_folder_id for rclone. This is the directory (identified by its Folder ID) that rclone considers to be the root of your drive.
Normally you will leave this blank and rclone will determine the correct root to use itself.
However you can set this to restrict rclone to a specific folder hierarchy or to access data within the "Computers" tab on the drive web interface (where files from Google's Backup and Sync desktop program go).
In order to do this you will have to find the Folder ID of the directory you wish rclone to display. This will be the last segment of the URL when you open the relevant folder in the drive web interface.
@@ -16919,10 +18962,20 @@ Import/Export of google documents
+| bmp |
+image/bmp |
+Windows Bitmap format |
+
+
| csv |
text/csv |
Standard CSV format for Spreadsheets |
+
+| doc |
+application/msword |
+Classic Word file |
+
| docx |
application/vnd.openxmlformats-officedocument.wordprocessingml.document |
@@ -16946,7 +18999,7 @@ Import/Export of google documents
| json |
application/vnd.google-apps.script+json |
-JSON Text Format |
+JSON Text Format for Google Apps scripts |
| odp |
@@ -16974,41 +19027,56 @@ Import/Export of google documents
Adobe PDF Format |
+| pjpeg |
+image/pjpeg |
+Progressive JPEG Image |
+
+
| png |
image/png |
PNG Image Format |
-
+
| pptx |
application/vnd.openxmlformats-officedocument.presentationml.presentation |
Microsoft Office Powerpoint |
-
+
| rtf |
application/rtf |
Rich Text Format |
-
+
| svg |
image/svg+xml |
Scalable Vector Graphics Format |
-
+
| tsv |
text/tab-separated-values |
Standard TSV format for spreadsheets |
-
+
| txt |
text/plain |
Plain Text |
+
+| wmf |
+application/x-msmetafile |
+Windows Meta File |
+
+| xls |
+application/vnd.ms-excel |
+Classic Excel file |
+
+
| xlsx |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
Microsoft Office Spreadsheet |
-
+
| zip |
application/zip |
A ZIP file of HTML, Images CSS |
@@ -17047,8 +19115,8 @@ Import/Export of google documents
-Standard options
-Here are the standard options specific to drive (Google Drive).
+Standard options
+Here are the Standard options specific to drive (Google Drive).
--drive-client-id
Google Application Client Id Setting your own is recommended. See https://rclone.org/drive/#making-your-own-client-id for how to create your own. If you leave this blank, it will use an internal key which is low performance.
Properties:
@@ -17104,16 +19172,6 @@ --drive-scope
---drive-root-folder-id
-ID of the root folder. Leave blank normally.
-Fill in to access "Computers" folders (see docs), or for rclone to use a non root folder as its starting point.
-Properties:
-
-- Config: root_folder_id
-- Env Var: RCLONE_DRIVE_ROOT_FOLDER_ID
-- Type: string
-- Required: false
-
--drive-service-account-file
Service Account Credentials JSON file path.
Leave blank normally. Needed only if you want use SA instead of interactive login.
@@ -17135,7 +19193,7 @@ --drive-alternate-export
Default: false
Advanced options
-Here are the advanced options specific to drive (Google Drive).
+Here are the Advanced options specific to drive (Google Drive).
--drive-token
OAuth Access Token as a JSON blob.
Properties:
@@ -17165,6 +19223,16 @@ --drive-token-url
Type: string
Required: false
+--drive-root-folder-id
+ID of the root folder. Leave blank normally.
+Fill in to access "Computers" folders (see docs), or for rclone to use a non root folder as its starting point.
+Properties:
+
+- Config: root_folder_id
+- Env Var: RCLONE_DRIVE_ROOT_FOLDER_ID
+- Type: string
+- Required: false
+
--drive-service-account-credentials
Service Account Credentials JSON blob.
Leave blank normally. Needed only if you want use SA instead of interactive login.
@@ -17490,6 +19558,21 @@ --drive-skip-dangling-shortcuts
Type: bool
Default: false
+--drive-resource-key
+Resource key for accessing a link-shared file.
+If you need to access files shared with a link like this
+https://drive.google.com/drive/folders/XXX?resourcekey=YYY&usp=sharing
+Then you will need to use the first part "XXX" as the "root_folder_id" and the second part "YYY" as the "resource_key" otherwise you will get 404 not found errors when trying to access the directory.
+See: https://developers.google.com/drive/api/guides/resource-keys
+This resource key requirement only applies to a subset of old files.
+Note also that opening the folder once in the web interface (with the user you've authenticated rclone with) seems to be enough so that the resource key is no needed.
+Properties:
+
+- Config: resource_key
+- Env Var: RCLONE_DRIVE_RESOURCE_KEY
+- Type: string
+- Required: false
+
--drive-encoding
The encoding for the backend.
See the encoding section in the overview for more info.
@@ -17505,7 +19588,7 @@ Backend commands
Run them with
rclone backend COMMAND remote:
The help below will explain what arguments each command takes.
-See the "rclone backend" command for more info on how to pass options and arguments.
+See the backend command for more info on how to pass options and arguments.
These can be run on a running backend using the rc command backend/command.
get
Get command for fetching the drive config parameters
@@ -17563,15 +19646,19 @@ drives
"name": "Test Drive"
}
]
-With the -o config parameter it will output the list in a format suitable for adding to a config file to make aliases for all the drives found.
+With the -o config parameter it will output the list in a format suitable for adding to a config file to make aliases for all the drives found and a combined drive.
[My Drive]
type = alias
remote = drive,team_drive=0ABCDEF-01234567890,root_folder_id=:
[Test Drive]
type = alias
-remote = drive,team_drive=0ABCDEFabcdefghijkl,root_folder_id=:
-Adding this to the rclone config file will cause those team drives to be accessible with the aliases shown. This may require manual editing of the names.
+remote = drive,team_drive=0ABCDEFabcdefghijkl,root_folder_id=:
+
+[AllDrives]
+type = combine
+remote = "My Drive=My Drive:" "Test Drive=Test Drive:"
+Adding this to the rclone config file will cause those team drives to be accessible with the aliases shown. Any illegal charactes will be substituted with "_" and duplicate names will have numbers suffixed. It will also add a remote called AllDrives which shows all the shared drives combined into one directory tree.
untrash
Untrash files and directories
rclone backend untrash remote: [options] [<arguments>+]
@@ -17597,11 +19684,17 @@ copyid
The path should end with a / to indicate copy the file as named to this directory. If it doesn't end with a / then the last path component will be used as the file name.
If the destination is a drive backend then server-side copying will be attempted if possible.
Use the -i flag to see what would be copied before copying.
+
+Dump the export formats for debug purposes
+rclone backend exportformats remote: [options] [<arguments>+]
+
+Dump the import formats for debug purposes
+rclone backend importformats remote: [options] [<arguments>+]
Limitations
Drive has quite a lot of rate limiting. This causes rclone to be limited to transferring about 2 files per second only. Individual files may be transferred much faster at 100s of MiB/s but lots of small files can take a long time.
Server side copies are also subject to a separate rate limit. If you see User rate limit exceeded errors, wait at least 24 hours and retry. You can disable server-side copies with --disable copy to download and upload the files if you prefer.
Limitations of Google Docs
-Google docs will appear as size -1 in rclone ls and as size 0 in anything which uses the VFS layer, e.g. rclone mount, rclone serve.
+Google docs will appear as size -1 in rclone ls, rclone ncdu etc, and as size 0 in anything which uses the VFS layer, e.g. rclone mount and rclone serve. When calculating directory totals, e.g. in rclone size and rclone ncdu, they will be counted in as empty files.
This is because rclone can't find out the size of the Google docs without downloading them.
Google docs will transfer correctly with rclone sync, rclone copy etc as rclone knows to ignore the size when doing the transfer.
However an unfortunate consequence of this is that you may not be able to download Google docs using rclone mount. If it doesn't work you will get a 0 sized file. If you try again the doc may gain its correct size and be downloadable. Whether it will work on not depends on the application accessing the mount and the OS you are running - experiment to find out if it does work for you!
@@ -17623,16 +19716,15 @@ Making your own client_id
Select a project or create a new project.
Under "ENABLE APIS AND SERVICES" search for "Drive", and enable the "Google Drive API".
Click "Credentials" in the left-side panel (not "Create credentials", which opens the wizard), then "Create credentials"
-If you already configured an "Oauth Consent Screen", then skip to the next step; if not, click on "CONFIGURE CONSENT SCREEN" button (near the top right corner of the right panel), then select "External" and click on "CREATE"; on the next screen, enter an "Application name" ("rclone" is OK); enter "User Support Email" (your own email is OK); enter "Developer Contact Email" (your own email is OK); then click on "Save" (all other data is optional). Click again on "Credentials" on the left panel to go back to the "Credentials" screen.
-
-(PS: if you are a GSuite user, you could also select "Internal" instead of "External" above, but this has not been tested/documented so far).
-
+If you already configured an "Oauth Consent Screen", then skip to the next step; if not, click on "CONFIGURE CONSENT SCREEN" button (near the top right corner of the right panel), then select "External" and click on "CREATE"; on the next screen, enter an "Application name" ("rclone" is OK); enter "User Support Email" (your own email is OK); enter "Developer Contact Email" (your own email is OK); then click on "Save" (all other data is optional). Click again on "Credentials" on the left panel to go back to the "Credentials" screen.
+(PS: if you are a GSuite user, you could also select "Internal" instead of "External" above, but this will restrict API use to Google Workspace users in your organisation).
Click on the "+ CREATE CREDENTIALS" button at the top of the screen, then select "OAuth client ID".
Choose an application type of "Desktop app" and click "Create". (the default name is fine)
-It will show you a client ID and client secret. Make a note of these.
+It will show you a client ID and client secret. Make a note of these.
+(If you selected "External" at Step 5 continue to "Publish App" in the Steps 9 and 10. If you chose "Internal" you don't need to publish and can skip straight to Step 11.)
Go to "Oauth consent screen" and press "Publish App"
-Provide the noted client ID and client secret to rclone.
Click "OAuth consent screen", then click "PUBLISH APP" button and confirm, or add your account under "Test users".
+Provide the noted client ID and client secret to rclone.
Be aware that, due to the "enhanced security" recently introduced by Google, you are theoretically expected to "submit your app for verification" and then wait a few weeks(!) for their response; in practice, you can go right ahead and use the client ID and client secret with rclone, the only issue will be a very scary confirmation screen shown when you connect via your browser for rclone to be able to get its token-id (but as this only happens during the remote configuration, it's not such a big deal).
(Thanks to @balazer on github for these instructions.)
@@ -17640,7 +19732,7 @@ Making your own client_id
Google Photos
The rclone backend for Google Photos is a specialized backend for transferring photos and videos to and from Google Photos.
NB The Google Photos API which rclone uses has quite a few limitations, so please read the limitations section carefully to make sure it is suitable for your use.
-Configuration
+Configuration
The initial setup for google cloud storage involves getting a token from Google Photos which you need to do in your browser. rclone config walks you through it.
Here is an example of how to make a remote called remote. First run:
rclone config
@@ -17793,8 +19885,8 @@ Layout
This means that you can use the album path pretty much like a normal filesystem and it is a good target for repeated syncing.
The shared-album directory shows albums shared with you or by you. This is similar to the Sharing tab in the Google Photos web interface.
-Standard options
-Here are the standard options specific to google photos (Google Photos).
+Standard options
+Here are the Standard options specific to google photos (Google Photos).
--gphotos-client-id
OAuth Client Id.
Leave blank normally.
@@ -17826,7 +19918,7 @@ --gphotos-read-only
Default: false
Advanced options
-Here are the advanced options specific to google photos (Google Photos).
+Here are the Advanced options specific to google photos (Google Photos).
--gphotos-token
OAuth Access Token as a JSON blob.
Properties:
@@ -18007,8 +20099,8 @@ Pre-Seed from a SUM File
rclone backend stickyimport hasher:path/to/data sha1 remote:/path/to/sum.sha1
stickyimport is similar to import but works much faster because it does not need to stat existing files and skips initial tree walk. Instead of binding cache entries to file fingerprints it creates sticky entries bound to the file name alone ignoring size, modification time etc. Such hash entries can be replaced only by purge, delete, backend drop or by full re-read/re-write of the files.
Configuration reference
-Standard options
-Here are the standard options specific to hasher (Better checksums for other remotes).
+Standard options
+Here are the Standard options specific to hasher (Better checksums for other remotes).
--hasher-remote
Remote to cache checksums for (e.g. myRemote:path).
Properties:
@@ -18037,7 +20129,7 @@ --hasher-max-age
Default: off
Advanced options
-Here are the advanced options specific to hasher (Better checksums for other remotes).
+Here are the Advanced options specific to hasher (Better checksums for other remotes).
--hasher-auto-size
Auto-update checksum for files smaller than this size (disabled by default).
Properties:
@@ -18047,12 +20139,15 @@ --hasher-auto-size
Type: SizeSuffix
Default: 0
+
+Any metadata supported by the underlying remote is read and written.
+See the metadata docs for more info.
Backend commands
Here are the commands specific to the hasher backend.
Run them with
rclone backend COMMAND remote:
The help below will explain what arguments each command takes.
-See the "rclone backend" command for more info on how to pass options and arguments.
+See the backend command for more info on how to pass options and arguments.
These can be run on a running backend using the rc command backend/command.
drop
Drop cache
@@ -18101,7 +20196,7 @@ Cache storage
HDFS
HDFS is a distributed file-system, part of the Apache Hadoop framework.
Paths are specified as remote: or remote:path/to/dir.
-Configuration
+Configuration
Here is an example of how to make a remote called remote. First run:
rclone config
This will guide you through an interactive setup process:
@@ -18209,8 +20304,8 @@ Restricted filename characters
Invalid UTF-8 bytes will also be replaced.
-Standard options
-Here are the standard options specific to hdfs (Hadoop distributed file system).
+Standard options
+Here are the Standard options specific to hdfs (Hadoop distributed file system).
--hdfs-namenode
Hadoop name node and port.
E.g. "namenode:8020" to connect to host namenode at port 8020.
@@ -18238,7 +20333,7 @@ --hdfs-username
Advanced options
-Here are the advanced options specific to hdfs (Hadoop distributed file system).
+Here are the Advanced options specific to hdfs (Hadoop distributed file system).
--hdfs-service-principal-name
Kerberos service principal name for the namenode.
Enables KERBEROS authentication. Specifies the Service Principal Name (SERVICE/FQDN) for the namenode. E.g. "hdfs/namenode.hadoop.docker" for namenode running as service 'hdfs' with FQDN 'namenode.hadoop.docker'.
@@ -18281,13 +20376,309 @@ Limitations
No server-side Move or DirMove.
Checksums not implemented.
+HiDrive
+Paths are specified as remote:path
+Paths may be as deep as required, e.g. remote:directory/subdirectory.
+The initial setup for hidrive involves getting a token from HiDrive which you need to do in your browser. rclone config walks you through it.
+Configuration
+Here is an example of how to make a remote called remote. First run:
+ rclone config
+This will guide you through an interactive setup process:
+No remotes found - make a new one
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+name> remote
+Type of storage to configure.
+Choose a number from below, or type in your own value
+[snip]
+XX / HiDrive
+ \ "hidrive"
+[snip]
+Storage> hidrive
+OAuth Client Id - Leave blank normally.
+client_id>
+OAuth Client Secret - Leave blank normally.
+client_secret>
+Access permissions that rclone should use when requesting access from HiDrive.
+Leave blank normally.
+scope_access>
+Edit advanced config?
+y/n> n
+Use auto config?
+y/n> y
+If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth?state=xxxxxxxxxxxxxxxxxxxxxx
+Log in and authorize rclone for access
+Waiting for code...
+Got code
+--------------------
+[remote]
+type = hidrive
+token = {"access_token":"xxxxxxxxxxxxxxxxxxxx","token_type":"Bearer","refresh_token":"xxxxxxxxxxxxxxxxxxxxxxx","expiry":"xxxxxxxxxxxxxxxxxxxxxxx"}
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+You should be aware that OAuth-tokens can be used to access your account and hence should not be shared with other persons. See the below section for more information.
+See the remote setup docs for how to set it up on a machine with no Internet browser available.
+Note that rclone runs a webserver on your local machine to collect the token as returned from HiDrive. This only runs from the moment it opens your browser to the moment you get back the verification code. The webserver runs on http://127.0.0.1:53682/. If local port 53682 is protected by a firewall you may need to temporarily unblock the firewall to complete authorization.
+Once configured you can then use rclone like this,
+List directories in top level of your HiDrive root folder
+rclone lsd remote:
+List all the files in your HiDrive filesystem
+rclone ls remote:
+To copy a local directory to a HiDrive directory called backup
+rclone copy /home/source remote:backup
+Keeping your tokens safe
+Any OAuth-tokens will be stored by rclone in the remote's configuration file as unencrypted text. Anyone can use a valid refresh-token to access your HiDrive filesystem without knowing your password. Therefore you should make sure no one else can access your configuration.
+It is possible to encrypt rclone's configuration file. You can find information on securing your configuration file by viewing the configuration encryption docs.
+Invalid refresh token
+As can be verified here, each refresh_token (for Native Applications) is valid for 60 days. If used to access HiDrivei, its validity will be automatically extended.
+This means that if you
+
+- Don't use the HiDrive remote for 60 days
+
+then rclone will return an error which includes a text that implies the refresh token is invalid or expired.
+To fix this you will need to authorize rclone to access your HiDrive account again.
+Using
+rclone config reconnect remote:
+the process is very similar to the process of initial setup exemplified before.
+Modified time and hashes
+HiDrive allows modification times to be set on objects accurate to 1 second.
+HiDrive supports its own hash type which is used to verify the integrety of file contents after successful transfers.
+Restricted filename characters
+HiDrive cannot store files or folders that include / (0x2F) or null-bytes (0x00) in their name. Any other characters can be used in the names of files or folders. Additionally, files or folders cannot be named either of the following: . or ..
+Therefore rclone will automatically replace these characters, if files or folders are stored or accessed with such names.
+You can read about how this filename encoding works in general here.
+Keep in mind that HiDrive only supports file or folder names with a length of 255 characters or less.
+Transfers
+HiDrive limits file sizes per single request to a maximum of 2 GiB. To allow storage of larger files and allow for better upload performance, the hidrive backend will use a chunked transfer for files larger than 96 MiB. Rclone will upload multiple parts/chunks of the file at the same time. Chunks in the process of being uploaded are buffered in memory, so you may want to restrict this behaviour on systems with limited resources.
+You can customize this behaviour using the following options:
+
+chunk_size: size of file parts
+upload_cutoff: files larger or equal to this in size will use a chunked transfer
+upload_concurrency: number of file-parts to upload at the same time
+
+See the below section about configuration options for more details.
+Root folder
+You can set the root folder for rclone. This is the directory that rclone considers to be the root of your HiDrive.
+Usually, you will leave this blank, and rclone will use the root of the account.
+However, you can set this to restrict rclone to a specific folder hierarchy.
+This works by prepending the contents of the root_prefix option to any paths accessed by rclone. For example, the following two ways to access the home directory are equivalent:
+rclone lsd --hidrive-root-prefix="/users/test/" remote:path
+
+rclone lsd remote:/users/test/path
+See the below section about configuration options for more details.
+Directory member count
+By default, rclone will know the number of directory members contained in a directory. For example, rclone lsd uses this information.
+The acquisition of this information will result in additional time costs for HiDrive's API. When dealing with large directory structures, it may be desirable to circumvent this time cost, especially when this information is not explicitly needed. For this, the disable_fetching_member_count option can be used.
+See the below section about configuration options for more details.
+Standard options
+Here are the Standard options specific to hidrive (HiDrive).
+--hidrive-client-id
+OAuth Client Id.
+Leave blank normally.
+Properties:
+
+- Config: client_id
+- Env Var: RCLONE_HIDRIVE_CLIENT_ID
+- Type: string
+- Required: false
+
+--hidrive-client-secret
+OAuth Client Secret.
+Leave blank normally.
+Properties:
+
+- Config: client_secret
+- Env Var: RCLONE_HIDRIVE_CLIENT_SECRET
+- Type: string
+- Required: false
+
+--hidrive-scope-access
+Access permissions that rclone should use when requesting access from HiDrive.
+Properties:
+
+- Config: scope_access
+- Env Var: RCLONE_HIDRIVE_SCOPE_ACCESS
+- Type: string
+- Default: "rw"
+- Examples:
+
+- "rw"
+
+- Read and write access to resources.
+
+- "ro"
+
+- Read-only access to resources.
+
+
+
+Advanced options
+Here are the Advanced options specific to hidrive (HiDrive).
+--hidrive-token
+OAuth Access Token as a JSON blob.
+Properties:
+
+- Config: token
+- Env Var: RCLONE_HIDRIVE_TOKEN
+- Type: string
+- Required: false
+
+--hidrive-auth-url
+Auth server URL.
+Leave blank to use the provider defaults.
+Properties:
+
+- Config: auth_url
+- Env Var: RCLONE_HIDRIVE_AUTH_URL
+- Type: string
+- Required: false
+
+--hidrive-token-url
+Token server url.
+Leave blank to use the provider defaults.
+Properties:
+
+- Config: token_url
+- Env Var: RCLONE_HIDRIVE_TOKEN_URL
+- Type: string
+- Required: false
+
+--hidrive-scope-role
+User-level that rclone should use when requesting access from HiDrive.
+Properties:
+
+- Config: scope_role
+- Env Var: RCLONE_HIDRIVE_SCOPE_ROLE
+- Type: string
+- Default: "user"
+- Examples:
+
+- "user"
+
+- User-level access to management permissions.
+- This will be sufficient in most cases.
+
+- "admin"
+
+- Extensive access to management permissions.
+
+- "owner"
+
+- Full access to management permissions.
+
+
+
+--hidrive-root-prefix
+The root/parent folder for all paths.
+Fill in to use the specified folder as the parent for all paths given to the remote. This way rclone can use any folder as its starting point.
+Properties:
+
+- Config: root_prefix
+- Env Var: RCLONE_HIDRIVE_ROOT_PREFIX
+- Type: string
+- Default: "/"
+- Examples:
+
+- "/"
+
+- The topmost directory accessible by rclone.
+- This will be equivalent with "root" if rclone uses a regular HiDrive user account.
+
+- "root"
+
+- The topmost directory of the HiDrive user account
+
+- ""
+
+- This specifies that there is no root-prefix for your paths.
+- When using this you will always need to specify paths to this remote with a valid parent e.g. "remote:/path/to/dir" or "remote:root/path/to/dir".
+
+
+
+--hidrive-endpoint
+Endpoint for the service.
+This is the URL that API-calls will be made to.
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_HIDRIVE_ENDPOINT
+- Type: string
+- Default: "https://api.hidrive.strato.com/2.1"
+
+--hidrive-disable-fetching-member-count
+Do not fetch number of objects in directories unless it is absolutely necessary.
+Requests may be faster if the number of objects in subdirectories is not fetched.
+Properties:
+
+- Config: disable_fetching_member_count
+- Env Var: RCLONE_HIDRIVE_DISABLE_FETCHING_MEMBER_COUNT
+- Type: bool
+- Default: false
+
+--hidrive-chunk-size
+Chunksize for chunked uploads.
+Any files larger than the configured cutoff (or files of unknown size) will be uploaded in chunks of this size.
+The upper limit for this is 2147483647 bytes (about 2.000Gi). That is the maximum amount of bytes a single upload-operation will support. Setting this above the upper limit or to a negative value will cause uploads to fail.
+Setting this to larger values may increase the upload speed at the cost of using more memory. It can be set to smaller values smaller to save on memory.
+Properties:
+
+- Config: chunk_size
+- Env Var: RCLONE_HIDRIVE_CHUNK_SIZE
+- Type: SizeSuffix
+- Default: 48Mi
+
+--hidrive-upload-cutoff
+Cutoff/Threshold for chunked uploads.
+Any files larger than this will be uploaded in chunks of the configured chunksize.
+The upper limit for this is 2147483647 bytes (about 2.000Gi). That is the maximum amount of bytes a single upload-operation will support. Setting this above the upper limit will cause uploads to fail.
+Properties:
+
+- Config: upload_cutoff
+- Env Var: RCLONE_HIDRIVE_UPLOAD_CUTOFF
+- Type: SizeSuffix
+- Default: 96Mi
+
+--hidrive-upload-concurrency
+Concurrency for chunked uploads.
+This is the upper limit for how many transfers for the same file are running concurrently. Setting this above to a value smaller than 1 will cause uploads to deadlock.
+If you are uploading small numbers of large files over high-speed links and these uploads do not fully utilize your bandwidth, then increasing this may help to speed up the transfers.
+Properties:
+
+- Config: upload_concurrency
+- Env Var: RCLONE_HIDRIVE_UPLOAD_CONCURRENCY
+- Type: int
+- Default: 4
+
+--hidrive-encoding
+The encoding for the backend.
+See the encoding section in the overview for more info.
+Properties:
+
+- Config: encoding
+- Env Var: RCLONE_HIDRIVE_ENCODING
+- Type: MultiEncoder
+- Default: Slash,Dot
+
+Limitations
+Symbolic links
+HiDrive is able to store symbolic links (symlinks) by design, for example, when unpacked from a zip archive.
+There exists no direct mechanism to manage native symlinks in remotes. As such this implementation has chosen to ignore any native symlinks present in the remote. rclone will not be able to access or show any symlinks stored in the hidrive-remote. This means symlinks cannot be individually removed, copied, or moved, except when removing, copying, or moving the parent folder.
+This does not affect the .rclonelink-files that rclone uses to encode and store symbolic links.
+Sparse files
+It is possible to store sparse files in HiDrive.
+Note that copying a sparse file will expand the holes into null-byte (0x00) regions that will then consume disk space. Likewise, when downloading a sparse file, the resulting file will have null-byte regions in the place of file holes.
HTTP
The HTTP remote is a read only remote for reading files of a webserver. The webserver should provide file listings which rclone will read and turn into a remote. This has been tested with common webservers such as Apache/Nginx/Caddy and will likely work with file listings from most web servers. (If it doesn't then please file an issue, or send a pull request!)
Paths are specified as remote: or remote:path.
The remote: represents the configured url, and any path following it will be resolved relative to this url, according to the URL standard. This means with remote url https://beta.rclone.org/branch and path fix, the resolved URL will be https://beta.rclone.org/branch/fix, while with path /fix the resolved URL will be https://beta.rclone.org/fix as the absolute path is resolved from the root of the domain.
If the path following the remote: ends with / it will be assumed to point to a directory. If the path does not end with /, then a HEAD request is sent and the response used to decide if it it is treated as a file or a directory (run with -vv to see details). When --http-no-head is specified, a path without ending / is always assumed to be a file. If rclone incorrectly assumes the path is a file, the solution is to specify the path with ending /. When you know the path is a directory, ending it with / is always better as it avoids the initial HEAD request.
To just download a single file it is easier to use copyurl.
-Configuration
+Configuration
Here is an example of how to make a remote called remote. First run:
rclone config
This will guide you through an interactive setup process:
@@ -18300,7 +20691,7 @@ Configuration
Type of storage to configure.
Choose a number from below, or type in your own value
[snip]
-XX / http Connection
+XX / HTTP
\ "http"
[snip]
Storage> http
@@ -18350,10 +20741,10 @@ Usage without a config file
rclone lsd --http-url https://beta.rclone.org :http:
or:
rclone lsd :http,url='https://beta.rclone.org':
-Standard options
-Here are the standard options specific to http (http Connection).
+Standard options
+Here are the Standard options specific to http (HTTP).
--http-url
-URL of http host to connect to.
+URL of HTTP host to connect to.
E.g. "https://example.com", or "https://user:pass@example.com" to use a username and password.
Properties:
@@ -18362,8 +20753,8 @@ --http-url
- Type: string
- Required: true
-Advanced options
-Here are the advanced options specific to http (http Connection).
+Advanced options
+Here are the Advanced options specific to http (HTTP).
Set HTTP headers for all transactions.
Use this to set additional HTTP headers for all transactions.
@@ -18405,13 +20796,13 @@ --http-no-head
Type: bool
Default: false
-Limitations
+Limitations
rclone about is not supported by the HTTP backend. Backends without this capability cannot determine free space for an rclone mount or use policy mfs (most free space) as a member of an rclone union remote.
-See List of backends that do not support rclone about See rclone about
+See List of backends that do not support rclone about and rclone about
Hubic
Paths are specified as remote:path
Paths are specified as remote:container (or remote: for the lsd command.) You may put subdirectories in too, e.g. remote:container/path/to/dir.
-Configuration
+Configuration
The initial setup for Hubic involves getting a token from Hubic which you need to do in your browser. rclone config walks you through it.
Here is an example of how to make a remote called remote. First run:
rclone config
@@ -18469,8 +20860,8 @@ Modified time
The modified time is stored as metadata on the object as X-Object-Meta-Mtime as floating point since the epoch accurate to 1 ns.
This is a de facto standard (used in the official python-swiftclient amongst others) for storing the modification time for an object.
Note that Hubic wraps the Swift backend, so most of the properties of are the same.
-Standard options
-Here are the standard options specific to hubic (Hubic).
+Standard options
+Here are the Standard options specific to hubic (Hubic).
--hubic-client-id
OAuth Client Id.
Leave blank normally.
@@ -18491,8 +20882,8 @@ --hubic-client-secret
Type: string
Required: false
-Advanced options
-Here are the advanced options specific to hubic (Hubic).
+Advanced options
+Here are the Advanced options specific to hubic (Hubic).
--hubic-token
OAuth Access Token as a JSON blob.
Properties:
@@ -18554,9 +20945,288 @@ --hubic-encoding
Type: MultiEncoder
Default: Slash,InvalidUtf8
-Limitations
+Limitations
This uses the normal OpenStack Swift mechanism to refresh the Swift API credentials and ignores the expires field returned by the Hubic API.
The Swift API doesn't return a correct MD5SUM for segmented files (Dynamic or Static Large Objects) so rclone won't check or use the MD5SUM for these.
+Internet Archive
+The Internet Archive backend utilizes Items on archive.org
+Refer to IAS3 API documentation for the API this backend uses.
+Paths are specified as remote:bucket (or remote: for the lsd command.) You may put subdirectories in too, e.g. remote:item/path/to/dir.
+Once you have made a remote (see the provider specific section above) you can use it like this:
+Unlike S3, listing up all items uploaded by you isn't supported.
+Make a new item
+rclone mkdir remote:item
+List the contents of a item
+rclone ls remote:item
+Sync /home/local/directory to the remote item, deleting any excess files in the item.
+rclone sync -i /home/local/directory remote:item
+Notes
+Because of Internet Archive's architecture, it enqueues write operations (and extra post-processings) in a per-item queue. You can check item's queue at https://catalogd.archive.org/history/item-name-here . Because of that, all uploads/deletes will not show up immediately and takes some time to be available. The per-item queue is enqueued to an another queue, Item Deriver Queue. You can check the status of Item Deriver Queue here. This queue has a limit, and it may block you from uploading, or even deleting. You should avoid uploading a lot of small files for better behavior.
+You can optionally wait for the server's processing to finish, by setting non-zero value to wait_archive key. By making it wait, rclone can do normal file comparison. Make sure to set a large enough value (e.g. 30m0s for smaller files) as it can take a long time depending on server's queue.
+
+This backend supports setting, updating and reading metadata of each file. The metadata will appear as file metadata on Internet Archive. However, some fields are reserved by both Internet Archive and rclone.
+The following are reserved by Internet Archive: - name - source - size - md5 - crc32 - sha1 - format - old_version - viruscheck
+Trying to set values to these keys is ignored with a warning. Only setting mtime is an exception. Doing so make it the identical behavior as setting ModTime.
+rclone reserves all the keys starting with rclone-. Setting value for these keys will give you warnings, but values are set according to request.
+If there are multiple values for a key, only the first one is returned. This is a limitation of rclone, that supports one value per one key. It can be triggered when you did a server-side copy.
+Reading metadata will also provide custom (non-standard nor reserved) ones.
+Configuration
+Here is an example of making an internetarchive configuration. Most applies to the other providers as well, any differences are described below.
+First run
+rclone config
+This will guide you through an interactive setup process.
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+name> remote
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+XX / InternetArchive Items
+ \ (internetarchive)
+Storage> internetarchive
+Option access_key_id.
+IAS3 Access Key.
+Leave blank for anonymous access.
+You can find one here: https://archive.org/account/s3.php
+Enter a value. Press Enter to leave empty.
+access_key_id> XXXX
+Option secret_access_key.
+IAS3 Secret Key (password).
+Leave blank for anonymous access.
+Enter a value. Press Enter to leave empty.
+secret_access_key> XXXX
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n> y
+Option endpoint.
+IAS3 Endpoint.
+Leave blank for default value.
+Enter a string value. Press Enter for the default (https://s3.us.archive.org).
+endpoint>
+Option front_endpoint.
+Host of InternetArchive Frontend.
+Leave blank for default value.
+Enter a string value. Press Enter for the default (https://archive.org).
+front_endpoint>
+Option disable_checksum.
+Don't store MD5 checksum with object metadata.
+Normally rclone will calculate the MD5 checksum of the input before
+uploading it so it can ask the server to check the object against checksum.
+This is great for data integrity checking but can cause long delays for
+large files to start uploading.
+Enter a boolean value (true or false). Press Enter for the default (true).
+disable_checksum> true
+Option encoding.
+The encoding for the backend.
+See the [encoding section in the overview](https://rclone.org/overview/#encoding) for more info.
+Enter a encoder.MultiEncoder value. Press Enter for the default (Slash,Question,Hash,Percent,Del,Ctl,InvalidUtf8,Dot).
+encoding>
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n> n
+--------------------
+[remote]
+type = internetarchive
+access_key_id = XXXX
+secret_access_key = XXXX
+--------------------
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+Standard options
+Here are the Standard options specific to internetarchive (Internet Archive).
+--internetarchive-access-key-id
+IAS3 Access Key.
+Leave blank for anonymous access. You can find one here: https://archive.org/account/s3.php
+Properties:
+
+- Config: access_key_id
+- Env Var: RCLONE_INTERNETARCHIVE_ACCESS_KEY_ID
+- Type: string
+- Required: false
+
+--internetarchive-secret-access-key
+IAS3 Secret Key (password).
+Leave blank for anonymous access.
+Properties:
+
+- Config: secret_access_key
+- Env Var: RCLONE_INTERNETARCHIVE_SECRET_ACCESS_KEY
+- Type: string
+- Required: false
+
+Advanced options
+Here are the Advanced options specific to internetarchive (Internet Archive).
+--internetarchive-endpoint
+IAS3 Endpoint.
+Leave blank for default value.
+Properties:
+
+- Config: endpoint
+- Env Var: RCLONE_INTERNETARCHIVE_ENDPOINT
+- Type: string
+- Default: "https://s3.us.archive.org"
+
+--internetarchive-front-endpoint
+Host of InternetArchive Frontend.
+Leave blank for default value.
+Properties:
+
+- Config: front_endpoint
+- Env Var: RCLONE_INTERNETARCHIVE_FRONT_ENDPOINT
+- Type: string
+- Default: "https://archive.org"
+
+--internetarchive-disable-checksum
+Don't ask the server to test against MD5 checksum calculated by rclone. Normally rclone will calculate the MD5 checksum of the input before uploading it so it can ask the server to check the object against checksum. This is great for data integrity checking but can cause long delays for large files to start uploading.
+Properties:
+
+- Config: disable_checksum
+- Env Var: RCLONE_INTERNETARCHIVE_DISABLE_CHECKSUM
+- Type: bool
+- Default: true
+
+--internetarchive-wait-archive
+Timeout for waiting the server's processing tasks (specifically archive and book_op) to finish. Only enable if you need to be guaranteed to be reflected after write operations. 0 to disable waiting. No errors to be thrown in case of timeout.
+Properties:
+
+- Config: wait_archive
+- Env Var: RCLONE_INTERNETARCHIVE_WAIT_ARCHIVE
+- Type: Duration
+- Default: 0s
+
+--internetarchive-encoding
+The encoding for the backend.
+See the encoding section in the overview for more info.
+Properties:
+
+- Config: encoding
+- Env Var: RCLONE_INTERNETARCHIVE_ENCODING
+- Type: MultiEncoder
+- Default: Slash,LtGt,CrLf,Del,Ctl,InvalidUtf8,Dot
+
+
+Metadata fields provided by Internet Archive. If there are multiple values for a key, only the first one is returned. This is a limitation of Rclone, that supports one value per one key.
+Owner is able to add custom keys. Metadata feature grabs all the keys including them.
+Here are the possible system metadata items for the internetarchive backend.
+
+
+
+
+
+
+
+
+
+
+
+
+
+| crc32 |
+CRC32 calculated by Internet Archive |
+string |
+01234567 |
+N |
+
+
+| format |
+Name of format identified by Internet Archive |
+string |
+Comma-Separated Values |
+N |
+
+
+| md5 |
+MD5 hash calculated by Internet Archive |
+string |
+01234567012345670123456701234567 |
+N |
+
+
+| mtime |
+Time of last modification, managed by Rclone |
+RFC 3339 |
+2006-01-02T15:04:05.999999999Z |
+N |
+
+
+| name |
+Full file path, without the bucket part |
+filename |
+backend/internetarchive/internetarchive.go |
+N |
+
+
+| old_version |
+Whether the file was replaced and moved by keep-old-version flag |
+boolean |
+true |
+N |
+
+
+| rclone-ia-mtime |
+Time of last modification, managed by Internet Archive |
+RFC 3339 |
+2006-01-02T15:04:05.999999999Z |
+N |
+
+
+| rclone-mtime |
+Time of last modification, managed by Rclone |
+RFC 3339 |
+2006-01-02T15:04:05.999999999Z |
+N |
+
+
+| rclone-update-track |
+Random value used by Rclone for tracking changes inside Internet Archive |
+string |
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
+N |
+
+
+| sha1 |
+SHA1 hash calculated by Internet Archive |
+string |
+0123456701234567012345670123456701234567 |
+N |
+
+
+| size |
+File size in bytes |
+decimal number |
+123456 |
+N |
+
+
+| source |
+The source of the file |
+string |
+original |
+N |
+
+
+| viruscheck |
+The last time viruscheck process was run for the file (?) |
+unixtime |
+1654191352 |
+N |
+
+
+
+See the metadata docs for more info.
Jottacloud
Jottacloud is a cloud storage service provider from a Norwegian company, using its own datacenters in Norway. In addition to the official service at jottacloud.com, it also provides white-label solutions to different companies, such as: * Telia * Telia Cloud (cloud.telia.se) * Telia Sky (sky.telia.no) * Tele2 * Tele2 Cloud (mittcloud.tele2.se) * Elkjøp (with subsidiaries): * Elkjøp Cloud (cloud.elkjop.no) * Elgiganten Sweden (cloud.elgiganten.se) * Elgiganten Denmark (cloud.elgiganten.dk) * Giganti Cloud (cloud.gigantti.fi) * ELKO Clouud (cloud.elko.is)
Most of the white-label versions are supported by this backend, although may require different authentication setup - described below.
@@ -18572,7 +21242,7 @@ Telia Cloud authentication
Similar to other whitelabel versions Telia Cloud doesn't offer the option of creating a CLI token, and additionally uses a separate authentication flow where the username is generated internally. To setup rclone to use Telia Cloud, choose Telia Cloud authentication in the setup. The rest of the setup is identical to the default setup.
Tele2 Cloud authentication
As Tele2-Com Hem merger was completed this authentication can be used for former Com Hem Cloud and Tele2 Cloud customers as no support for creating a CLI token exists, and additionally uses a separate authentication flow where the username is generated internally. To setup rclone to use Tele2 Cloud, choose Tele2 Cloud authentication in the setup. The rest of the setup is identical to the default setup.
-Configuration
+Configuration
Here is an example of how to make a remote called remote with the default setup. First run:
rclone config
This will guide you through an interactive setup process:
@@ -18582,56 +21252,78 @@ Configuration
q) Quit config
n/s/q> n
name> remote
+Option Storage.
Type of storage to configure.
-Enter a string value. Press Enter for the default ("").
-Choose a number from below, or type in your own value
+Choose a number from below, or type in your own value.
[snip]
XX / Jottacloud
- \ "jottacloud"
+ \ (jottacloud)
[snip]
Storage> jottacloud
-** See help for jottacloud backend at: https://rclone.org/jottacloud/ **
-
-Edit advanced config? (y/n)
-y) Yes
-n) No
-y/n> n
-Remote config
-Use legacy authentication?.
-This is only required for certain whitelabel versions of Jottacloud and not recommended for normal users.
+Edit advanced config?
y) Yes
n) No (default)
y/n> n
-
-Generate a personal login token here: https://www.jottacloud.com/web/secure
+Option config_type.
+Select authentication type.
+Choose a number from below, or type in an existing string value.
+Press Enter for the default (standard).
+ / Standard authentication.
+ 1 | Use this if you're a normal Jottacloud user.
+ \ (standard)
+ / Legacy authentication.
+ 2 | This is only required for certain whitelabel versions of Jottacloud and not recommended for normal users.
+ \ (legacy)
+ / Telia Cloud authentication.
+ 3 | Use this if you are using Telia Cloud.
+ \ (telia)
+ / Tele2 Cloud authentication.
+ 4 | Use this if you are using Tele2 Cloud.
+ \ (tele2)
+config_type> 1
+Personal login token.
+Generate here: https://www.jottacloud.com/web/secure
Login Token> <your token here>
-
-Do you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?
-
+Use a non-standard device/mountpoint?
+Choosing no, the default, will let you access the storage used for the archive
+section of the official Jottacloud client. If you instead want to access the
+sync or the backup section, for example, you must choose yes.
y) Yes
-n) No
+n) No (default)
y/n> y
-Please select the device to use. Normally this will be Jotta
-Choose a number from below, or type in an existing value
+Option config_device.
+The device to use. In standard setup the built-in Jotta device is used,
+which contains predefined mountpoints for archive, sync etc. All other devices
+are treated as backup devices by the official Jottacloud client. You may create
+a new by entering a unique name.
+Choose a number from below, or type in your own string value.
+Press Enter for the default (DESKTOP-3H31129).
1 > DESKTOP-3H31129
2 > Jotta
-Devices> 2
-Please select the mountpoint to user. Normally this will be Archive
-Choose a number from below, or type in an existing value
+config_device> 2
+Option config_mountpoint.
+The mountpoint to use for the built-in device Jotta.
+The standard setup is to use the Archive mountpoint. Most other mountpoints
+have very limited support in rclone and should generally be avoided.
+Choose a number from below, or type in an existing string value.
+Press Enter for the default (Archive).
1 > Archive
- 2 > Links
+ 2 > Shared
3 > Sync
-
-Mountpoints> 1
+config_mountpoint> 1
--------------------
-[jotta]
+[remote]
type = jottacloud
+configVersion = 1
+client_id = jottacli
+client_secret =
+tokenURL = https://id.jottacloud.com/auth/realms/jottacloud/protocol/openid-connect/token
token = {........}
+username = 2940e57271a93d987d6f8a21
device = Jotta
mountpoint = Archive
-configVersion = 1
--------------------
-y) Yes this is OK
+y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> y
@@ -18643,18 +21335,19 @@ Configuration
To copy a local directory to an Jottacloud directory called backup
rclone copy /home/source remote:backup
Devices and Mountpoints
-The official Jottacloud client registers a device for each computer you install it on, and then creates a mountpoint for each folder you select for Backup. The web interface uses a special device called Jotta for the Archive and Sync mountpoints.
-With rclone you'll want to use the Jotta/Archive device/mountpoint in most cases, however if you want to access files uploaded by any of the official clients rclone provides the option to select other devices and mountpoints during config. Note that uploading files is currently not supported to other devices than Jotta.
-The built-in Jotta device may also contain several other mountpoints, such as: Latest, Links, Shared and Trash. These are special mountpoints with a different internal representation than the "regular" mountpoints. Rclone will only to a very limited degree support them. Generally you should avoid these, unless you know what you are doing.
+The official Jottacloud client registers a device for each computer you install it on, and shows them in the backup section of the user interface. For each folder you select for backup it will create a mountpoint within this device. A built-in device called Jotta is special, and contains mountpoints Archive, Sync and some others, used for corresponding features in official clients.
+With rclone you'll want to use the standard Jotta/Archive device/mountpoint in most cases. However, you may for example want to access files from the sync or backup functionality provided by the official clients, and rclone therefore provides the option to select other devices and mountpoints during config.
+You are allowed to create new devices and mountpoints. All devices except the built-in Jotta device are treated as backup devices by official Jottacloud clients, and the mountpoints on them are individual backup sets.
+With the built-in Jotta device, only existing, built-in, mountpoints can be selected. In addition to the mentioned Archive and Sync, it may contain several other mountpoints such as: Latest, Links, Shared and Trash. All of these are special mountpoints with a different internal representation than the "regular" mountpoints. Rclone will only to a very limited degree support them. Generally you should avoid these, unless you know what you are doing.
--fast-list
This remote supports --fast-list which allows you to use fewer transactions in exchange for more memory. See the rclone docs for more details.
Note that the implementation in Jottacloud always uses only a single API request to get the entire list, so for large folders this could lead to long wait time before the first results are shown.
Note also that with rclone version 1.58 and newer information about MIME types are not available when using --fast-list.
-Modified time and hashes
+Modified time and hashes
Jottacloud allows modification times to be set on objects accurate to 1 second. These will be used to detect whether objects need syncing or not.
Jottacloud supports MD5 type hashes, so you can use the --checksum flag.
Note that Jottacloud requires the MD5 hash before upload so if the source does not have an MD5 checksum then the file will be cached temporarily on disk (in location given by --temp-dir) before it is uploaded. Small files will be cached in memory - see the --jottacloud-md5-memory-limit flag. When uploading from local disk the source checksum is always available, so this does not apply. Starting with rclone version 1.52 the same is true for crypted remotes (in older versions the crypt backend would not calculate hashes for uploads from local disk, so the Jottacloud backend had to do it as described above).
-Restricted filename characters
+Restricted filename characters
In addition to the default restricted characters set the following characters are also replaced:
@@ -18710,8 +21403,8 @@ Versions
Versioning can be disabled by --jottacloud-no-versions option. This is achieved by deleting the remote file prior to uploading a new version. If the upload the fails no version of the file will be available in the remote.
To view your current quota you can use the rclone about remote: command which will display your usage limit (unless it is unlimited) and the current usage.
-Advanced options
-Here are the advanced options specific to jottacloud (Jottacloud).
+Advanced options
+Here are the Advanced options specific to jottacloud (Jottacloud).
--jottacloud-md5-memory-limit
Files bigger than this will be cached on disk to calculate the MD5 if required.
Properties:
@@ -18768,7 +21461,7 @@ --jottacloud-encoding
Type: MultiEncoder
Default: Slash,LtGt,DoubleQuote,Colon,Question,Asterisk,Pipe,Del,Ctl,InvalidUtf8,Dot
-Limitations
+Limitations
Note that Jottacloud is case insensitive so you can't have a file called "Hello.doc" and one called "hello.doc".
There are quite a few characters that can't be in Jottacloud file names. Rclone will map these names to and from an identical looking unicode equivalent. For example if a file has a ? in it will be mapped to ? instead.
Jottacloud only supports filenames up to 255 characters in length.
@@ -18777,7 +21470,7 @@ Troubleshooting
Koofr
Paths are specified as remote:path
Paths may be as deep as required, e.g. remote:directory/subdirectory.
-Configuration
+Configuration
The initial setup for Koofr involves creating an application password for rclone. You can do that by opening the Koofr web application, giving the password a nice name like rclone and clicking on generate.
Here is an example of how to make a remote called koofr. First run:
rclone config
@@ -18845,7 +21538,7 @@ Configuration
rclone ls koofr:
To copy a local directory to an Koofr directory called backup
rclone copy /home/source koofr:backup
-Restricted filename characters
+Restricted filename characters
In addition to the default restricted characters set the following characters are also replaced:
@@ -18864,8 +21557,8 @@ Restricted filename characters
Invalid UTF-8 bytes will also be replaced, as they can't be used in XML strings.
-Standard options
-Here are the standard options specific to koofr (Koofr, Digi Storage and other Koofr-compatible storage providers).
+Standard options
+Here are the Standard options specific to koofr (Koofr, Digi Storage and other Koofr-compatible storage providers).
--koofr-provider
Choose your storage provider.
Properties:
@@ -18942,8 +21635,8 @@ --koofr-password
Type: string
Required: true
-Advanced options
-Here are the advanced options specific to koofr (Koofr, Digi Storage and other Koofr-compatible storage providers).
+Advanced options
+Here are the Advanced options specific to koofr (Koofr, Digi Storage and other Koofr-compatible storage providers).
--koofr-mountid
Mount ID of the mount to use.
If omitted, the primary mount is used.
@@ -18974,7 +21667,7 @@ --koofr-encoding
Type: MultiEncoder
Default: Slash,BackSlash,Del,Ctl,InvalidUtf8,Dot
-Limitations
+Limitations
Note that Koofr is case insensitive so you can't have a file called "Hello.doc" and one called "hello.doc".
Providers
Koofr
@@ -19116,7 +21809,7 @@ Features highlights
Storage keeps hash for all files and performs transparent deduplication, the hash algorithm is a modified SHA1
If a particular file is already present in storage, one can quickly submit file hash instead of long file upload (this optimization is supported by rclone)
-Configuration
+Configuration
Here is an example of making a mailru configuration. First create a Mail.ru Cloud account and choose a tariff, then run
rclone config
This will guide you through an interactive setup process:
@@ -19190,7 +21883,7 @@ Emptying Trash
Removing a file or directory actually moves it to the trash, which is not visible to rclone but can be seen in a web browser. The trashed file still occupies part of total quota. If you wish to empty your trash and free some quota, you can use the rclone cleanup remote: command, which will permanently delete all your trashed files. This command does not take any path arguments.
To view your current quota you can use the rclone about remote: command which will display your usage limit (quota) and the current usage.
-Restricted filename characters
+Restricted filename characters
In addition to the default restricted characters set the following characters are also replaced:
@@ -19244,8 +21937,8 @@ Restricted filename characters
Invalid UTF-8 bytes will also be replaced, as they can't be used in JSON strings.
-Standard options
-Here are the standard options specific to mailru (Mail.ru Cloud).
+Standard options
+Here are the Standard options specific to mailru (Mail.ru Cloud).
--mailru-user
User name (usually email).
Properties:
@@ -19286,8 +21979,8 @@ --mailru-speedup-enable
-Advanced options
-Here are the advanced options specific to mailru (Mail.ru Cloud).
+Advanced options
+Here are the Advanced options specific to mailru (Mail.ru Cloud).
--mailru-speedup-file-patterns
Comma separated list of file name patterns eligible for speedup (put by hash).
Patterns are case insensitive and can contain '*' or '?' meta characters.
@@ -19416,7 +22109,7 @@ --mailru-encoding
Type: MultiEncoder
Default: Slash,LtGt,DoubleQuote,Colon,Question,Asterisk,Pipe,BackSlash,Del,Ctl,InvalidUtf8,Dot
-Limitations
+Limitations
File size limits depend on your account. A single file size is limited by 2G for a free account and unlimited for paid tariffs. Please refer to the Mail.ru site for the total uploaded size limits.
Note that Mailru is case insensitive so you can't have a file called "Hello.doc" and one called "hello.doc".
Mega
@@ -19424,7 +22117,7 @@ Mega
This is an rclone backend for Mega which supports the file transfer features of Mega using the same client side encryption.
Paths are specified as remote:path
Paths may be as deep as required, e.g. remote:directory/subdirectory.
-Configuration
+Configuration
Here is an example of how to make a remote called remote. First run:
rclone config
This will guide you through an interactive setup process:
@@ -19471,9 +22164,9 @@ Configuration
rclone ls remote:
To copy a local directory to an Mega directory called backup
rclone copy /home/source remote:backup
-Modified time and hashes
+Modified time and hashes
Mega does not support modification times or hashes yet.
-Restricted filename characters
+Restricted filename characters