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:
Nick Craig-Wood 2017-08-07 21:10:03 +01:00
parent bced73c947
commit ec2ea37ad2
5 changed files with 132 additions and 5 deletions

View file

@ -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.
### --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 ###
Do a trial run with no permanent changes. Use this to see what rclone

View file

@ -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
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.
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.
### Duplicated files ###

View file

@ -100,6 +100,7 @@ var (
tpsLimit = Float64P("tpslimit", "", 0, "Limit HTTP transactions per second to this.")
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.")
disableFeatures = StringP("disable", "", "", "Disable a comma separated list of features. Use help to see a list.")
logLevel = LogLevelNotice
statsLogLevel = LogLevelInfo
bwLimit BwTimetable
@ -235,6 +236,7 @@ type ConfigInfo struct {
TPSLimit float64
TPSLimitBurst int
BindAddr net.IP
DisableFeatures []string
}
// Return the path to the configuration file
@ -410,6 +412,13 @@ func LoadConfig() {
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.
var err error
ConfigPath, err = filepath.Abs(*configFile)

View file

@ -8,8 +8,10 @@ import (
"math"
"os"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"time"
"github.com/pkg/errors"
@ -347,6 +349,46 @@ type Features struct {
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
// optional interfaces. It returns the original updated Features
// struct passed in.
@ -387,7 +429,7 @@ func (ft *Features) Fill(f Fs) *Features {
if do, ok := f.(ListRer); ok {
ft.ListR = do.ListR
}
return ft
return ft.DisableList(Config.DisableFeatures)
}
// Mask the Features with the Fs passed in
@ -438,7 +480,7 @@ func (ft *Features) Mask(f Fs) *Features {
if mask.ListR == nil {
ft.ListR = nil
}
return ft
return ft.DisableList(Config.DisableFeatures)
}
// Wrap makes a Copy of the features passed in, overriding the UnWrap

54
fs/fs_test.go Normal file
View 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)
}