mirror of
https://github.com/rclone/rclone
synced 2024-12-24 15:43:45 +01:00
filter: Add BoundedRecursion method
This indicates that the filter set could be satisfied by a bounded directory recursion.
This commit is contained in:
parent
bb5ac8efbe
commit
047f00a411
@ -21,8 +21,9 @@ var Active = mustNewFilter(nil)
|
|||||||
|
|
||||||
// rule is one filter rule
|
// rule is one filter rule
|
||||||
type rule struct {
|
type rule struct {
|
||||||
Include bool
|
Include bool
|
||||||
Regexp *regexp.Regexp
|
Regexp *regexp.Regexp
|
||||||
|
boundedRecursion bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true if rule matches path
|
// Match returns true if rule matches path
|
||||||
@ -46,13 +47,14 @@ type rules struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add adds a rule if it doesn't exist already
|
// add adds a rule if it doesn't exist already
|
||||||
func (rs *rules) add(Include bool, re *regexp.Regexp) {
|
func (rs *rules) add(Include bool, re *regexp.Regexp, boundedRecursion bool) {
|
||||||
if rs.existing == nil {
|
if rs.existing == nil {
|
||||||
rs.existing = make(map[string]struct{})
|
rs.existing = make(map[string]struct{})
|
||||||
}
|
}
|
||||||
newRule := rule{
|
newRule := rule{
|
||||||
Include: Include,
|
Include: Include,
|
||||||
Regexp: re,
|
Regexp: re,
|
||||||
|
boundedRecursion: boundedRecursion,
|
||||||
}
|
}
|
||||||
newRuleString := newRule.String()
|
newRuleString := newRule.String()
|
||||||
if _, ok := rs.existing[newRuleString]; ok {
|
if _, ok := rs.existing[newRuleString]; ok {
|
||||||
@ -73,6 +75,23 @@ func (rs *rules) len() int {
|
|||||||
return len(rs.rules)
|
return len(rs.rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// boundedRecursion returns true if the set of filters would only
|
||||||
|
// need bounded recursion to evaluate
|
||||||
|
func (rs *rules) boundedRecursion() bool {
|
||||||
|
var (
|
||||||
|
excludeAll = false
|
||||||
|
boundedRecursion = true
|
||||||
|
)
|
||||||
|
for _, rule := range rs.rules {
|
||||||
|
if rule.Include {
|
||||||
|
boundedRecursion = boundedRecursion && rule.boundedRecursion
|
||||||
|
} else if rule.Regexp.String() == `^.*$` {
|
||||||
|
excludeAll = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return excludeAll && boundedRecursion
|
||||||
|
}
|
||||||
|
|
||||||
// FilesMap describes the map of files to transfer
|
// FilesMap describes the map of files to transfer
|
||||||
type FilesMap map[string]struct{}
|
type FilesMap map[string]struct{}
|
||||||
|
|
||||||
@ -232,7 +251,8 @@ func (f *Filter) addDirGlobs(Include bool, glob string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.dirRules.add(Include, dirRe)
|
boundedRecursion := globBoundedRecursion(dirGlob)
|
||||||
|
f.dirRules.add(Include, dirRe, boundedRecursion)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -248,8 +268,9 @@ func (f *Filter) Add(Include bool, glob string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
boundedRecursion := globBoundedRecursion(glob)
|
||||||
if isFileRule {
|
if isFileRule {
|
||||||
f.fileRules.add(Include, re)
|
f.fileRules.add(Include, re, boundedRecursion)
|
||||||
// If include rule work out what directories are needed to scan
|
// If include rule work out what directories are needed to scan
|
||||||
// if exclude rule, we can't rule anything out
|
// if exclude rule, we can't rule anything out
|
||||||
// Unless it is `*` which matches everything
|
// Unless it is `*` which matches everything
|
||||||
@ -262,7 +283,7 @@ func (f *Filter) Add(Include bool, glob string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isDirRule {
|
if isDirRule {
|
||||||
f.dirRules.add(Include, re)
|
f.dirRules.add(Include, re, boundedRecursion)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -343,6 +364,12 @@ func (f *Filter) InActive() bool {
|
|||||||
len(f.Opt.ExcludeFile) == 0)
|
len(f.Opt.ExcludeFile) == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BoundedRecursion returns true if the filter can be evaluated with
|
||||||
|
// bounded recursion only.
|
||||||
|
func (f *Filter) BoundedRecursion() bool {
|
||||||
|
return f.fileRules.boundedRecursion()
|
||||||
|
}
|
||||||
|
|
||||||
// includeRemote returns whether this remote passes the filter rules.
|
// includeRemote returns whether this remote passes the filter rules.
|
||||||
func (f *Filter) includeRemote(remote string) bool {
|
func (f *Filter) includeRemote(remote string) bool {
|
||||||
for _, rule := range f.fileRules.rules {
|
for _, rule := range f.fileRules.rules {
|
||||||
|
@ -25,6 +25,7 @@ func TestNewFilterDefault(t *testing.T) {
|
|||||||
assert.Len(t, f.dirRules.rules, 0)
|
assert.Len(t, f.dirRules.rules, 0)
|
||||||
assert.Nil(t, f.files)
|
assert.Nil(t, f.files)
|
||||||
assert.True(t, f.InActive())
|
assert.True(t, f.InActive())
|
||||||
|
assert.False(t, f.BoundedRecursion())
|
||||||
}
|
}
|
||||||
|
|
||||||
// testFile creates a temp file with the contents
|
// testFile creates a temp file with the contents
|
||||||
@ -103,6 +104,38 @@ func TestNewFilterFull(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.False(t, f.InActive())
|
assert.False(t, f.InActive())
|
||||||
|
assert.False(t, f.BoundedRecursion())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterBoundedRecursion(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"", false},
|
||||||
|
{"- /**", true},
|
||||||
|
{"+ *.jpg", false},
|
||||||
|
{"+ *.jpg\n- /**", false},
|
||||||
|
{"+ /*.jpg\n- /**", true},
|
||||||
|
{"+ *.png\n+ /*.jpg\n- /**", false},
|
||||||
|
{"+ /*.png\n+ /*.jpg\n- /**", true},
|
||||||
|
{"- *.jpg\n- /**", true},
|
||||||
|
{"+ /*.jpg\n- /**", true},
|
||||||
|
{"+ /*dir/\n- /**", true},
|
||||||
|
{"+ /*dir/\n", false},
|
||||||
|
{"+ /*dir/**\n- /**", false},
|
||||||
|
{"+ **/pics*/*.jpg\n- /**", false},
|
||||||
|
} {
|
||||||
|
f, err := NewFilter(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, rule := range strings.Split(test.in, "\n") {
|
||||||
|
if rule != "" {
|
||||||
|
require.NoError(t, f.AddRule(rule))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
got := f.BoundedRecursion()
|
||||||
|
assert.Equal(t, test.want, got, test.in)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type includeTest struct {
|
type includeTest struct {
|
||||||
@ -151,6 +184,7 @@ func TestNewFilterIncludeFiles(t *testing.T) {
|
|||||||
{"file3.jpg", 3, 0, false},
|
{"file3.jpg", 3, 0, false},
|
||||||
})
|
})
|
||||||
assert.False(t, f.InActive())
|
assert.False(t, f.InActive())
|
||||||
|
assert.False(t, f.BoundedRecursion())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFilterIncludeFilesDirs(t *testing.T) {
|
func TestNewFilterIncludeFilesDirs(t *testing.T) {
|
||||||
@ -278,6 +312,7 @@ func TestNewFilterMinSize(t *testing.T) {
|
|||||||
{"potato/file2.jpg", 99, 0, false},
|
{"potato/file2.jpg", 99, 0, false},
|
||||||
})
|
})
|
||||||
assert.False(t, f.InActive())
|
assert.False(t, f.InActive())
|
||||||
|
assert.False(t, f.BoundedRecursion())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFilterMaxSize(t *testing.T) {
|
func TestNewFilterMaxSize(t *testing.T) {
|
||||||
|
@ -167,3 +167,15 @@ func globToDirGlobs(glob string) (out []string) {
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// globBoundedRecursion returns true if the glob only needs bounded
|
||||||
|
// recursion in the file tree to evaluate.
|
||||||
|
func globBoundedRecursion(glob string) bool {
|
||||||
|
if strings.Contains(glob, "**") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(glob, "/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -108,3 +108,45 @@ func TestGlobToDirGlobs(t *testing.T) {
|
|||||||
assert.Equal(t, test.want, got, test.in)
|
assert.Equal(t, test.want, got, test.in)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGlobBoundedRecursion(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{`*`, false},
|
||||||
|
{`/*`, true},
|
||||||
|
{`/**`, false},
|
||||||
|
{`*.jpg`, false},
|
||||||
|
{`/*.jpg`, true},
|
||||||
|
{`/a/*.jpg`, true},
|
||||||
|
{`/a/b/*.jpg`, true},
|
||||||
|
{`*/*/*.jpg`, false},
|
||||||
|
{`a/b/`, false},
|
||||||
|
{`a/b`, false},
|
||||||
|
{`a/b/*.{png,gif}`, false},
|
||||||
|
{`/a/{jpg,png,gif}/*.{jpg,true,gif}`, true},
|
||||||
|
{`a/{a,a*b,a**c}/d/`, false},
|
||||||
|
{`/a/{a,a*b,a/c,d}/d/`, true},
|
||||||
|
{`**`, false},
|
||||||
|
{`a**`, false},
|
||||||
|
{`a**b`, false},
|
||||||
|
{`a**b**c**d`, false},
|
||||||
|
{`a**b/c**d`, false},
|
||||||
|
{`/A/a**b/B/c**d/C/`, false},
|
||||||
|
{`/var/spool/**/ncw`, false},
|
||||||
|
{`var/spool/**/ncw/`, false},
|
||||||
|
{"/file1.jpg", true},
|
||||||
|
{"/file2.png", true},
|
||||||
|
{"/*.jpg", true},
|
||||||
|
{"/*.png", true},
|
||||||
|
{"/potato", true},
|
||||||
|
{"/sausage1", true},
|
||||||
|
{"/sausage2*", true},
|
||||||
|
{"/sausage3**", false},
|
||||||
|
{"/a/*.jpg", true},
|
||||||
|
} {
|
||||||
|
got := globBoundedRecursion(test.in)
|
||||||
|
assert.Equal(t, test.want, got, test.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user