forked from TrueCloudLab/rclone
fs: Add --disable flag to disable optional features - fixes #1551
Eg to disable server side copy use `--disable copy`, to see a list of what you can disable, `--disable help`.
This commit is contained in:
parent
bced73c947
commit
ec2ea37ad2
5 changed files with 132 additions and 5 deletions
|
@ -365,6 +365,27 @@ connection to go through to a remote object storage system. It is
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
The features can be put in in any case.
|
||||||
|
|
||||||
|
To see a list of which features can be disabled use:
|
||||||
|
|
||||||
|
--disable help
|
||||||
|
|
||||||
|
See the overview [features](/overview/#features) and
|
||||||
|
[optional features](/overview/#optional-features) to get an idea of
|
||||||
|
which feature does what.
|
||||||
|
|
||||||
|
This flag can be useful for debugging and in exceptional circumstances
|
||||||
|
(eg Google Drive limiting the total volume of Server Side Copies to
|
||||||
|
100GB/day).
|
||||||
|
|
||||||
### -n, --dry-run ###
|
### -n, --dry-run ###
|
||||||
|
|
||||||
Do a trial run with no permanent changes. Use this to see what rclone
|
Do a trial run with no permanent changes. Use this to see what rclone
|
||||||
|
|
|
@ -275,9 +275,10 @@ limited to transferring about 2 files per second only. Individual
|
||||||
files may be transferred much faster at 100s of MBytes/s but lots of
|
files may be transferred much faster at 100s of MBytes/s but lots of
|
||||||
small files can take a long time.
|
small files can take a long time.
|
||||||
|
|
||||||
Server side copies are also subject to a separate rate limit. If
|
Server side copies are also subject to a separate rate limit. If you
|
||||||
you see User rate limit exceeded errors, wait at least 24 hours and
|
see User rate limit exceeded errors, wait at least 24 hours and retry.
|
||||||
retry.
|
You can disable server side copies with `--disable copy` to download
|
||||||
|
and upload the files if you prefer.
|
||||||
|
|
||||||
### Duplicated files ###
|
### Duplicated files ###
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ var (
|
||||||
tpsLimit = Float64P("tpslimit", "", 0, "Limit HTTP transactions per second to this.")
|
tpsLimit = Float64P("tpslimit", "", 0, "Limit HTTP transactions per second to this.")
|
||||||
tpsLimitBurst = IntP("tpslimit-burst", "", 1, "Max burst of transactions for --tpslimit.")
|
tpsLimitBurst = IntP("tpslimit-burst", "", 1, "Max burst of transactions for --tpslimit.")
|
||||||
bindAddr = StringP("bind", "", "", "Local address to bind to for outgoing connections, IPv4, IPv6 or name.")
|
bindAddr = StringP("bind", "", "", "Local address to bind to for outgoing connections, IPv4, IPv6 or name.")
|
||||||
|
disableFeatures = StringP("disable", "", "", "Disable a comma separated list of features. Use help to see a list.")
|
||||||
logLevel = LogLevelNotice
|
logLevel = LogLevelNotice
|
||||||
statsLogLevel = LogLevelInfo
|
statsLogLevel = LogLevelInfo
|
||||||
bwLimit BwTimetable
|
bwLimit BwTimetable
|
||||||
|
@ -235,6 +236,7 @@ type ConfigInfo struct {
|
||||||
TPSLimit float64
|
TPSLimit float64
|
||||||
TPSLimitBurst int
|
TPSLimitBurst int
|
||||||
BindAddr net.IP
|
BindAddr net.IP
|
||||||
|
DisableFeatures []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the path to the configuration file
|
// Return the path to the configuration file
|
||||||
|
@ -410,6 +412,13 @@ func LoadConfig() {
|
||||||
Config.BindAddr = addrs[0]
|
Config.BindAddr = addrs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *disableFeatures != "" {
|
||||||
|
if *disableFeatures == "help" {
|
||||||
|
log.Fatalf("Possible backend features are: %s\n", strings.Join(new(Features).List(), ", "))
|
||||||
|
}
|
||||||
|
Config.DisableFeatures = strings.Split(*disableFeatures, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// Load configuration file.
|
// Load configuration file.
|
||||||
var err error
|
var err error
|
||||||
ConfigPath, err = filepath.Abs(*configFile)
|
ConfigPath, err = filepath.Abs(*configFile)
|
||||||
|
|
46
fs/fs.go
46
fs/fs.go
|
@ -8,8 +8,10 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -347,6 +349,46 @@ type Features struct {
|
||||||
ListR ListRFn
|
ListR ListRFn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable nil's out the named feature. If it isn't found then it
|
||||||
|
// will log a message.
|
||||||
|
func (ft *Features) Disable(name string) *Features {
|
||||||
|
v := reflect.ValueOf(ft).Elem()
|
||||||
|
vType := v.Type()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
vName := vType.Field(i).Name
|
||||||
|
field := v.Field(i)
|
||||||
|
if strings.EqualFold(name, vName) {
|
||||||
|
if !field.CanSet() {
|
||||||
|
Errorf(nil, "Can't set Feature %q", name)
|
||||||
|
} else {
|
||||||
|
zero := reflect.Zero(field.Type())
|
||||||
|
field.Set(zero)
|
||||||
|
Debugf(nil, "Reset feature %q", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ft
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a slice of all the possible feature names
|
||||||
|
func (ft *Features) List() (out []string) {
|
||||||
|
v := reflect.ValueOf(ft).Elem()
|
||||||
|
vType := v.Type()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
out = append(out, vType.Field(i).Name)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableList nil's out the comma separated list of named features.
|
||||||
|
// If it isn't found then it will log a message.
|
||||||
|
func (ft *Features) DisableList(list []string) *Features {
|
||||||
|
for _, feature := range list {
|
||||||
|
ft.Disable(strings.TrimSpace(feature))
|
||||||
|
}
|
||||||
|
return ft
|
||||||
|
}
|
||||||
|
|
||||||
// Fill fills in the function pointers in the Features struct from the
|
// Fill fills in the function pointers in the Features struct from the
|
||||||
// optional interfaces. It returns the original updated Features
|
// optional interfaces. It returns the original updated Features
|
||||||
// struct passed in.
|
// struct passed in.
|
||||||
|
@ -387,7 +429,7 @@ func (ft *Features) Fill(f Fs) *Features {
|
||||||
if do, ok := f.(ListRer); ok {
|
if do, ok := f.(ListRer); ok {
|
||||||
ft.ListR = do.ListR
|
ft.ListR = do.ListR
|
||||||
}
|
}
|
||||||
return ft
|
return ft.DisableList(Config.DisableFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mask the Features with the Fs passed in
|
// Mask the Features with the Fs passed in
|
||||||
|
@ -438,7 +480,7 @@ func (ft *Features) Mask(f Fs) *Features {
|
||||||
if mask.ListR == nil {
|
if mask.ListR == nil {
|
||||||
ft.ListR = nil
|
ft.ListR = nil
|
||||||
}
|
}
|
||||||
return ft
|
return ft.DisableList(Config.DisableFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap makes a Copy of the features passed in, overriding the UnWrap
|
// Wrap makes a Copy of the features passed in, overriding the UnWrap
|
||||||
|
|
54
fs/fs_test.go
Normal file
54
fs/fs_test.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFeaturesDisable(t *testing.T) {
|
||||||
|
ft := new(Features)
|
||||||
|
ft.Copy = func(src Object, remote string) (Object, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ft.CaseInsensitive = true
|
||||||
|
|
||||||
|
assert.NotNil(t, ft.Copy)
|
||||||
|
assert.Nil(t, ft.Purge)
|
||||||
|
ft.Disable("copy")
|
||||||
|
assert.Nil(t, ft.Copy)
|
||||||
|
assert.Nil(t, ft.Purge)
|
||||||
|
|
||||||
|
assert.True(t, ft.CaseInsensitive)
|
||||||
|
assert.False(t, ft.DuplicateFiles)
|
||||||
|
ft.Disable("caseinsensitive")
|
||||||
|
assert.False(t, ft.CaseInsensitive)
|
||||||
|
assert.False(t, ft.DuplicateFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFeaturesList(t *testing.T) {
|
||||||
|
ft := new(Features)
|
||||||
|
names := strings.Join(ft.List(), ",")
|
||||||
|
assert.True(t, strings.Contains(names, ",Copy,"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFeaturesDisableList(t *testing.T) {
|
||||||
|
ft := new(Features)
|
||||||
|
ft.Copy = func(src Object, remote string) (Object, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ft.CaseInsensitive = true
|
||||||
|
|
||||||
|
assert.NotNil(t, ft.Copy)
|
||||||
|
assert.Nil(t, ft.Purge)
|
||||||
|
assert.True(t, ft.CaseInsensitive)
|
||||||
|
assert.False(t, ft.DuplicateFiles)
|
||||||
|
|
||||||
|
ft.DisableList([]string{"copy", "caseinsensitive"})
|
||||||
|
|
||||||
|
assert.Nil(t, ft.Copy)
|
||||||
|
assert.Nil(t, ft.Purge)
|
||||||
|
assert.False(t, ft.CaseInsensitive)
|
||||||
|
assert.False(t, ft.DuplicateFiles)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue