2018-01-12 16:30:54 +00:00
|
|
|
// Package filter controls the filtering of files
|
|
|
|
package filter
|
2015-09-27 15:13:20 +00:00
|
|
|
|
|
|
|
import (
|
2019-06-17 08:34:30 +00:00
|
|
|
"context"
|
2021-11-04 10:12:57 +00:00
|
|
|
"errors"
|
2015-09-27 15:13:20 +00:00
|
|
|
"fmt"
|
2018-01-12 16:30:54 +00:00
|
|
|
"log"
|
2016-04-21 19:06:21 +00:00
|
|
|
"path"
|
2015-09-27 15:13:20 +00:00
|
|
|
"strings"
|
2015-12-17 11:52:38 +00:00
|
|
|
"time"
|
2015-09-27 15:13:20 +00:00
|
|
|
|
2019-07-28 17:47:38 +00:00
|
|
|
"github.com/rclone/rclone/fs"
|
2018-12-13 10:47:09 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
2015-09-27 15:13:20 +00:00
|
|
|
)
|
|
|
|
|
2020-11-26 17:10:41 +00:00
|
|
|
// This is the globally active filter
|
|
|
|
//
|
|
|
|
// This is accessed through GetConfig and AddConfig
|
|
|
|
var globalConfig = mustNewFilter(nil)
|
2015-09-27 15:13:20 +00:00
|
|
|
|
2024-07-04 16:29:50 +00:00
|
|
|
// OptionsInfo describes the Options in use
|
|
|
|
var OptionsInfo = fs.Options{{
|
|
|
|
Name: "delete_excluded",
|
|
|
|
Default: false,
|
|
|
|
Help: "Delete files on dest excluded from sync",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "exclude_if_present",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Exclude directories if filename is present",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "files_from",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read list of source-file names from file (use - to read from stdin)",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "files_from_raw",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read list of source-file names from file without any processing of lines (use - to read from stdin)",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "min_age",
|
|
|
|
Default: fs.DurationOff,
|
|
|
|
Help: "Only transfer files older than this in s or suffix ms|s|m|h|d|w|M|y",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "max_age",
|
|
|
|
Default: fs.DurationOff,
|
|
|
|
Help: "Only transfer files younger than this in s or suffix ms|s|m|h|d|w|M|y",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "min_size",
|
|
|
|
Default: fs.SizeSuffix(-1),
|
|
|
|
Help: "Only transfer files bigger than this in KiB or suffix B|K|M|G|T|P",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "max_size",
|
|
|
|
Default: fs.SizeSuffix(-1),
|
|
|
|
Help: "Only transfer files smaller than this in KiB or suffix B|K|M|G|T|P",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "ignore_case",
|
|
|
|
Default: false,
|
|
|
|
Help: "Ignore case in filters (case insensitive)",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "filter",
|
|
|
|
Default: []string{},
|
|
|
|
ShortOpt: "f",
|
|
|
|
Help: "Add a file filtering rule",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "filter_from",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read file filtering patterns from a file (use - to read from stdin)",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "exclude",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Exclude files matching pattern",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "exclude_from",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read file exclude patterns from file (use - to read from stdin)",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "include",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Include files matching pattern",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "include_from",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read file include patterns from file (use - to read from stdin)",
|
|
|
|
Groups: "Filter",
|
|
|
|
}, {
|
|
|
|
Name: "metadata_filter",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Add a metadata filtering rule",
|
|
|
|
Groups: "Filter,Metadata",
|
|
|
|
}, {
|
|
|
|
Name: "metadata_filter_from",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read metadata filtering patterns from a file (use - to read from stdin)",
|
|
|
|
Groups: "Filter,Metadata",
|
|
|
|
}, {
|
|
|
|
Name: "metadata_exclude",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Exclude metadatas matching pattern",
|
|
|
|
Groups: "Filter,Metadata",
|
|
|
|
}, {
|
|
|
|
Name: "metadata_exclude_from",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read metadata exclude patterns from file (use - to read from stdin)",
|
|
|
|
Groups: "Filter,Metadata",
|
|
|
|
}, {
|
|
|
|
Name: "metadata_include",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Include metadatas matching pattern",
|
|
|
|
Groups: "Filter,Metadata",
|
|
|
|
}, {
|
|
|
|
Name: "metadata_include_from",
|
|
|
|
Default: []string{},
|
|
|
|
Help: "Read metadata include patterns from file (use - to read from stdin)",
|
|
|
|
Groups: "Filter,Metadata",
|
|
|
|
}}
|
|
|
|
|
2024-07-04 10:30:04 +00:00
|
|
|
// Options configures the filter
|
|
|
|
type Options struct {
|
2024-07-04 16:29:50 +00:00
|
|
|
DeleteExcluded bool `config:"delete_excluded"`
|
|
|
|
RulesOpt // embedded so we don't change the JSON API
|
|
|
|
ExcludeFile []string `config:"exclude_if_present"`
|
|
|
|
FilesFrom []string `config:"files_from"`
|
|
|
|
FilesFromRaw []string `config:"files_from_raw"`
|
|
|
|
MetaRules RulesOpt `config:"metadata"`
|
|
|
|
MinAge fs.Duration `config:"min_age"`
|
|
|
|
MaxAge fs.Duration `config:"max_age"`
|
|
|
|
MinSize fs.SizeSuffix `config:"min_size"`
|
|
|
|
MaxSize fs.SizeSuffix `config:"max_size"`
|
|
|
|
IgnoreCase bool `config:"ignore_case"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: "filter", Opt: &Opt, Options: OptionsInfo, Reload: Reload})
|
2015-09-27 15:13:20 +00:00
|
|
|
}
|
|
|
|
|
2024-07-04 16:29:50 +00:00
|
|
|
// Opt is the default config for the filter
|
|
|
|
var Opt = Options{
|
|
|
|
MinAge: fs.DurationOff, // These have to be set here as the options are parsed once before the defaults are set
|
2018-03-12 20:52:42 +00:00
|
|
|
MaxAge: fs.DurationOff,
|
2018-01-12 16:30:54 +00:00
|
|
|
MinSize: fs.SizeSuffix(-1),
|
|
|
|
MaxSize: fs.SizeSuffix(-1),
|
2015-12-17 11:52:38 +00:00
|
|
|
}
|
|
|
|
|
2022-10-08 16:56:04 +00:00
|
|
|
// FilesMap describes the map of files to transfer
|
|
|
|
type FilesMap map[string]struct{}
|
|
|
|
|
2018-01-12 16:30:54 +00:00
|
|
|
// Filter describes any filtering in operation
|
|
|
|
type Filter struct {
|
2024-07-04 10:30:04 +00:00
|
|
|
Opt Options
|
2018-01-12 16:30:54 +00:00
|
|
|
ModTimeFrom time.Time
|
|
|
|
ModTimeTo time.Time
|
|
|
|
fileRules rules
|
|
|
|
dirRules rules
|
2022-08-04 17:19:05 +00:00
|
|
|
metaRules rules
|
2018-01-12 16:30:54 +00:00
|
|
|
files FilesMap // files if filesFrom
|
|
|
|
dirs FilesMap // dirs from filesFrom
|
|
|
|
}
|
2015-12-17 11:52:38 +00:00
|
|
|
|
2018-01-12 16:30:54 +00:00
|
|
|
// NewFilter parses the command line options and creates a Filter
|
|
|
|
// object. If opt is nil, then DefaultOpt will be used
|
2024-07-04 10:30:04 +00:00
|
|
|
func NewFilter(opt *Options) (f *Filter, err error) {
|
2018-01-12 16:30:54 +00:00
|
|
|
f = &Filter{}
|
2015-12-17 11:52:38 +00:00
|
|
|
|
2018-01-12 16:30:54 +00:00
|
|
|
// Make a copy of the options
|
|
|
|
if opt != nil {
|
|
|
|
f.Opt = *opt
|
|
|
|
} else {
|
2024-07-04 16:29:50 +00:00
|
|
|
f.Opt = Opt
|
2018-01-12 16:30:54 +00:00
|
|
|
}
|
2015-12-17 11:52:38 +00:00
|
|
|
|
2018-01-12 16:30:54 +00:00
|
|
|
// Filter flags
|
2018-03-12 20:52:42 +00:00
|
|
|
if f.Opt.MinAge.IsSet() {
|
2018-01-12 16:30:54 +00:00
|
|
|
f.ModTimeTo = time.Now().Add(-time.Duration(f.Opt.MinAge))
|
|
|
|
fs.Debugf(nil, "--min-age %v to %v", f.Opt.MinAge, f.ModTimeTo)
|
2015-09-27 15:13:20 +00:00
|
|
|
}
|
2018-03-12 20:52:42 +00:00
|
|
|
if f.Opt.MaxAge.IsSet() {
|
2018-01-12 16:30:54 +00:00
|
|
|
f.ModTimeFrom = time.Now().Add(-time.Duration(f.Opt.MaxAge))
|
2018-04-26 08:17:22 +00:00
|
|
|
if !f.ModTimeTo.IsZero() && f.ModTimeTo.Before(f.ModTimeFrom) {
|
2024-07-04 16:29:50 +00:00
|
|
|
log.Fatalf("filter: --min-age %q can't be larger than --max-age %q", opt.MinAge, opt.MaxAge)
|
2018-01-12 16:30:54 +00:00
|
|
|
}
|
|
|
|
fs.Debugf(nil, "--max-age %v to %v", f.Opt.MaxAge, f.ModTimeFrom)
|
|
|
|
}
|
|
|
|
|
2022-10-08 16:56:04 +00:00
|
|
|
err = parseRules(&f.Opt.RulesOpt, f.Add, f.Clear)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2016-01-10 11:42:53 +00:00
|
|
|
}
|
2019-10-09 12:06:46 +00:00
|
|
|
|
2022-08-04 17:19:05 +00:00
|
|
|
err = parseRules(&f.Opt.MetaRules, f.metaRules.Add, f.metaRules.clear)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-09 12:06:46 +00:00
|
|
|
inActive := f.InActive()
|
2020-04-03 09:36:24 +00:00
|
|
|
|
2018-01-12 16:30:54 +00:00
|
|
|
for _, rule := range f.Opt.FilesFrom {
|
2019-10-09 12:06:46 +00:00
|
|
|
if !inActive {
|
2022-06-08 20:54:39 +00:00
|
|
|
return nil, fmt.Errorf("the usage of --files-from overrides all other filters, it should be used alone or with --files-from-raw")
|
2020-04-03 09:36:24 +00:00
|
|
|
}
|
|
|
|
f.initAddFile() // init to show --files-from set even if no files within
|
|
|
|
err := forEachLine(rule, false, func(line string) error {
|
|
|
|
return f.AddFile(line)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rule := range f.Opt.FilesFromRaw {
|
|
|
|
// --files-from-raw can be used with --files-from, hence we do
|
|
|
|
// not need to get the value of f.InActive again
|
|
|
|
if !inActive {
|
2022-06-08 20:54:39 +00:00
|
|
|
return nil, fmt.Errorf("the usage of --files-from-raw overrides all other filters, it should be used alone or with --files-from")
|
2019-10-09 12:06:46 +00:00
|
|
|
}
|
2018-01-12 16:30:54 +00:00
|
|
|
f.initAddFile() // init to show --files-from set even if no files within
|
2020-04-03 09:36:24 +00:00
|
|
|
err := forEachLine(rule, true, func(line string) error {
|
2018-01-12 16:30:54 +00:00
|
|
|
return f.AddFile(line)
|
|
|
|
})
|
2015-12-17 11:52:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2020-04-03 09:36:24 +00:00
|
|
|
|
2020-11-05 11:33:32 +00:00
|
|
|
if fs.GetConfig(context.Background()).Dump&fs.DumpFilters != 0 {
|
2015-09-27 15:13:20 +00:00
|
|
|
fmt.Println("--- start filters ---")
|
|
|
|
fmt.Println(f.DumpFilters())
|
|
|
|
fmt.Println("--- end filters ---")
|
|
|
|
}
|
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
|
2024-07-04 10:30:04 +00:00
|
|
|
func mustNewFilter(opt *Options) *Filter {
|
2018-01-12 16:30:54 +00:00
|
|
|
f, err := NewFilter(opt)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
2016-05-16 16:14:04 +00:00
|
|
|
// addDirGlobs adds directory globs from the file glob passed in
|
|
|
|
func (f *Filter) addDirGlobs(Include bool, glob string) error {
|
|
|
|
for _, dirGlob := range globToDirGlobs(glob) {
|
|
|
|
// Don't add "/" as we always include the root
|
|
|
|
if dirGlob == "/" {
|
|
|
|
continue
|
|
|
|
}
|
2023-11-03 19:45:37 +00:00
|
|
|
dirRe, err := GlobPathToRegexp(dirGlob, f.Opt.IgnoreCase)
|
2016-05-16 16:14:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-08-08 12:35:13 +00:00
|
|
|
f.dirRules.add(Include, dirRe)
|
2016-05-16 16:14:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-27 15:13:20 +00:00
|
|
|
// Add adds a filter rule with include or exclude status indicated
|
|
|
|
func (f *Filter) Add(Include bool, glob string) error {
|
2016-05-16 16:14:04 +00:00
|
|
|
isDirRule := strings.HasSuffix(glob, "/")
|
|
|
|
isFileRule := !isDirRule
|
2020-10-27 13:08:20 +00:00
|
|
|
// Make excluding "dir/" equivalent to excluding "dir/**"
|
|
|
|
if isDirRule && !Include {
|
|
|
|
glob += "**"
|
|
|
|
}
|
2016-12-07 11:17:10 +00:00
|
|
|
if strings.Contains(glob, "**") {
|
2016-05-16 16:14:04 +00:00
|
|
|
isDirRule, isFileRule = true, true
|
|
|
|
}
|
2023-11-03 19:45:37 +00:00
|
|
|
re, err := GlobPathToRegexp(glob, f.Opt.IgnoreCase)
|
2015-09-27 15:13:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-05-16 16:14:04 +00:00
|
|
|
if isFileRule {
|
2019-08-08 12:35:13 +00:00
|
|
|
f.fileRules.add(Include, re)
|
2016-05-16 16:14:04 +00:00
|
|
|
// If include rule work out what directories are needed to scan
|
|
|
|
// if exclude rule, we can't rule anything out
|
|
|
|
// Unless it is `*` which matches everything
|
|
|
|
// NB ** and /** are DirRules
|
|
|
|
if Include || glob == "*" {
|
|
|
|
err = f.addDirGlobs(Include, glob)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if isDirRule {
|
2019-08-08 12:35:13 +00:00
|
|
|
f.dirRules.add(Include, re)
|
2015-09-27 15:13:20 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddRule adds a filter rule with include/exclude indicated by the prefix
|
|
|
|
//
|
|
|
|
// These are
|
|
|
|
//
|
|
|
|
// - glob
|
2022-08-05 15:35:41 +00:00
|
|
|
// - glob
|
|
|
|
// !
|
2015-09-27 15:13:20 +00:00
|
|
|
//
|
|
|
|
// '+' includes the glob, '-' excludes it and '!' resets the filter list
|
|
|
|
//
|
|
|
|
// Line comments may be introduced with '#' or ';'
|
|
|
|
func (f *Filter) AddRule(rule string) error {
|
2022-10-08 16:56:04 +00:00
|
|
|
return addRule(rule, f.Add, f.Clear)
|
2015-09-27 15:13:20 +00:00
|
|
|
}
|
|
|
|
|
2017-03-04 10:11:15 +00:00
|
|
|
// initAddFile creates f.files and f.dirs
|
|
|
|
func (f *Filter) initAddFile() {
|
2015-09-27 15:13:20 +00:00
|
|
|
if f.files == nil {
|
2016-10-07 10:39:39 +00:00
|
|
|
f.files = make(FilesMap)
|
|
|
|
f.dirs = make(FilesMap)
|
2015-09-27 15:13:20 +00:00
|
|
|
}
|
2017-03-04 10:11:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddFile adds a single file to the files from list
|
|
|
|
func (f *Filter) AddFile(file string) error {
|
|
|
|
f.initAddFile()
|
2015-09-27 15:13:20 +00:00
|
|
|
file = strings.Trim(file, "/")
|
|
|
|
f.files[file] = struct{}{}
|
2016-04-21 19:06:21 +00:00
|
|
|
// Put all the parent directories into f.dirs
|
|
|
|
for {
|
|
|
|
file = path.Dir(file)
|
|
|
|
if file == "." {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if _, found := f.dirs[file]; found {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
f.dirs[file] = struct{}{}
|
|
|
|
}
|
2015-09-27 15:13:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-10-07 10:39:39 +00:00
|
|
|
// Files returns all the files from the `--files-from` list
|
|
|
|
//
|
|
|
|
// It may be nil if the list is empty
|
|
|
|
func (f *Filter) Files() FilesMap {
|
|
|
|
return f.files
|
|
|
|
}
|
|
|
|
|
2015-09-27 15:13:20 +00:00
|
|
|
// Clear clears all the filter rules
|
|
|
|
func (f *Filter) Clear() {
|
2016-05-16 16:14:04 +00:00
|
|
|
f.fileRules.clear()
|
|
|
|
f.dirRules.clear()
|
2022-08-04 17:19:05 +00:00
|
|
|
f.metaRules.clear()
|
2015-09-27 15:13:20 +00:00
|
|
|
}
|
|
|
|
|
2016-02-25 19:58:00 +00:00
|
|
|
// InActive returns false if any filters are active
|
|
|
|
func (f *Filter) InActive() bool {
|
|
|
|
return (f.files == nil &&
|
|
|
|
f.ModTimeFrom.IsZero() &&
|
|
|
|
f.ModTimeTo.IsZero() &&
|
2018-01-12 16:30:54 +00:00
|
|
|
f.Opt.MinSize < 0 &&
|
|
|
|
f.Opt.MaxSize < 0 &&
|
2016-05-16 16:14:04 +00:00
|
|
|
f.fileRules.len() == 0 &&
|
2017-11-16 14:37:00 +00:00
|
|
|
f.dirRules.len() == 0 &&
|
2022-08-04 17:19:05 +00:00
|
|
|
f.metaRules.len() == 0 &&
|
2018-01-12 16:30:54 +00:00
|
|
|
len(f.Opt.ExcludeFile) == 0)
|
2016-02-25 19:58:00 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 15:34:16 +00:00
|
|
|
// IncludeRemote returns whether this remote passes the filter rules.
|
|
|
|
func (f *Filter) IncludeRemote(remote string) bool {
|
2022-08-11 11:20:17 +00:00
|
|
|
// filesFrom takes precedence
|
|
|
|
if f.files != nil {
|
|
|
|
_, include := f.files[remote]
|
|
|
|
return include
|
|
|
|
}
|
2022-10-08 16:56:04 +00:00
|
|
|
return f.fileRules.include(remote)
|
2016-04-21 19:06:21 +00:00
|
|
|
}
|
|
|
|
|
2017-11-16 14:38:00 +00:00
|
|
|
// ListContainsExcludeFile checks if exclude file is present in the list.
|
2018-01-12 16:30:54 +00:00
|
|
|
func (f *Filter) ListContainsExcludeFile(entries fs.DirEntries) bool {
|
|
|
|
if len(f.Opt.ExcludeFile) == 0 {
|
2017-11-16 14:38:00 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, entry := range entries {
|
2018-01-12 16:30:54 +00:00
|
|
|
obj, ok := entry.(fs.Object)
|
2017-11-16 14:38:00 +00:00
|
|
|
if ok {
|
|
|
|
basename := path.Base(obj.Remote())
|
2022-06-08 07:29:01 +00:00
|
|
|
for _, excludeFile := range f.Opt.ExcludeFile {
|
|
|
|
if basename == excludeFile {
|
|
|
|
return true
|
|
|
|
}
|
2017-11-16 14:38:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-11-09 09:28:36 +00:00
|
|
|
// IncludeDirectory returns a function which checks whether this
|
|
|
|
// directory should be included in the sync or not.
|
2019-06-17 08:34:30 +00:00
|
|
|
func (f *Filter) IncludeDirectory(ctx context.Context, fs fs.Fs) func(string) (bool, error) {
|
2017-11-09 09:28:36 +00:00
|
|
|
return func(remote string) (bool, error) {
|
|
|
|
remote = strings.Trim(remote, "/")
|
|
|
|
// first check if we need to remove directory based on
|
|
|
|
// the exclude file
|
2019-06-17 08:34:30 +00:00
|
|
|
excl, err := f.DirContainsExcludeFile(ctx, fs, remote)
|
2017-11-09 09:28:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if excl {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// filesFrom takes precedence
|
|
|
|
if f.files != nil {
|
|
|
|
_, include := f.dirs[remote]
|
|
|
|
return include, nil
|
|
|
|
}
|
|
|
|
remote += "/"
|
2022-10-08 16:56:04 +00:00
|
|
|
return f.dirRules.include(remote), nil
|
2016-04-21 19:06:21 +00:00
|
|
|
}
|
2017-11-09 09:28:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DirContainsExcludeFile checks if exclude file is present in a
|
2020-05-20 10:39:20 +00:00
|
|
|
// directory. If fs is nil, it works properly if ExcludeFile is an
|
2017-11-09 09:28:36 +00:00
|
|
|
// empty string (for testing).
|
2019-06-17 08:34:30 +00:00
|
|
|
func (f *Filter) DirContainsExcludeFile(ctx context.Context, fremote fs.Fs, remote string) (bool, error) {
|
2018-01-12 16:30:54 +00:00
|
|
|
if len(f.Opt.ExcludeFile) > 0 {
|
2022-06-08 07:29:01 +00:00
|
|
|
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
|
|
|
|
}
|
2016-05-16 16:14:04 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-09 09:28:36 +00:00
|
|
|
return false, nil
|
2016-04-21 19:06:21 +00:00
|
|
|
}
|
|
|
|
|
2015-09-27 15:13:20 +00:00
|
|
|
// Include returns whether this object should be included into the
|
2024-01-22 16:46:26 +00:00
|
|
|
// sync or not and logs the reason for exclusion if not included
|
2022-08-04 17:19:05 +00:00
|
|
|
func (f *Filter) Include(remote string, size int64, modTime time.Time, metadata fs.Metadata) bool {
|
2015-09-27 15:13:20 +00:00
|
|
|
// filesFrom takes precedence
|
|
|
|
if f.files != nil {
|
|
|
|
_, include := f.files[remote]
|
2024-01-22 16:46:26 +00:00
|
|
|
if !include {
|
|
|
|
fs.Debugf(remote, "Excluded (FilesFrom Filter)")
|
|
|
|
}
|
2015-09-27 15:13:20 +00:00
|
|
|
return include
|
|
|
|
}
|
2015-12-17 11:52:38 +00:00
|
|
|
if !f.ModTimeFrom.IsZero() && modTime.Before(f.ModTimeFrom) {
|
2024-01-22 16:46:26 +00:00
|
|
|
fs.Debugf(remote, "Excluded (ModTime Filter)")
|
2015-12-17 11:52:38 +00:00
|
|
|
return false
|
|
|
|
}
|
2015-12-17 14:22:43 +00:00
|
|
|
if !f.ModTimeTo.IsZero() && modTime.After(f.ModTimeTo) {
|
2024-01-22 16:46:26 +00:00
|
|
|
fs.Debugf(remote, "Excluded (ModTime Filter)")
|
2015-12-17 11:52:38 +00:00
|
|
|
return false
|
|
|
|
}
|
2018-01-12 16:30:54 +00:00
|
|
|
if f.Opt.MinSize >= 0 && size < int64(f.Opt.MinSize) {
|
2024-01-22 16:46:26 +00:00
|
|
|
fs.Debugf(remote, "Excluded (Size Filter)")
|
2015-09-27 15:13:20 +00:00
|
|
|
return false
|
|
|
|
}
|
2018-01-12 16:30:54 +00:00
|
|
|
if f.Opt.MaxSize >= 0 && size > int64(f.Opt.MaxSize) {
|
2024-01-22 16:46:26 +00:00
|
|
|
fs.Debugf(remote, "Excluded (Size Filter)")
|
2015-09-27 15:13:20 +00:00
|
|
|
return false
|
|
|
|
}
|
2022-08-04 17:19:05 +00:00
|
|
|
if f.metaRules.len() > 0 {
|
|
|
|
metadatas := make([]string, 0, len(metadata)+1)
|
|
|
|
for key, value := range metadata {
|
|
|
|
metadatas = append(metadatas, fmt.Sprintf("%s=%s", key, value))
|
|
|
|
}
|
|
|
|
if len(metadata) == 0 {
|
|
|
|
// If there is no metadata, add a null one
|
|
|
|
// otherwise the default action isn't taken
|
|
|
|
metadatas = append(metadatas, "\x00=\x00")
|
|
|
|
}
|
|
|
|
if !f.metaRules.includeMany(metadatas) {
|
2024-01-22 16:46:26 +00:00
|
|
|
fs.Debugf(remote, "Excluded (Metadata Filter)")
|
2022-08-04 17:19:05 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2024-01-22 16:46:26 +00:00
|
|
|
include := f.IncludeRemote(remote)
|
|
|
|
if !include {
|
|
|
|
fs.Debugf(remote, "Excluded (Path Filter)")
|
|
|
|
}
|
|
|
|
return include
|
2015-09-27 15:13:20 +00:00
|
|
|
}
|
|
|
|
|
2015-12-17 11:52:38 +00:00
|
|
|
// IncludeObject returns whether this object should be included into
|
|
|
|
// the sync or not. This is a convenience function to avoid calling
|
|
|
|
// o.ModTime(), which is an expensive operation.
|
2019-06-17 08:34:30 +00:00
|
|
|
func (f *Filter) IncludeObject(ctx context.Context, o fs.Object) bool {
|
2015-12-17 11:52:38 +00:00
|
|
|
var modTime time.Time
|
|
|
|
|
2015-12-17 14:22:43 +00:00
|
|
|
if !f.ModTimeFrom.IsZero() || !f.ModTimeTo.IsZero() {
|
2019-06-17 08:34:30 +00:00
|
|
|
modTime = o.ModTime(ctx)
|
2015-12-17 11:52:38 +00:00
|
|
|
} else {
|
|
|
|
modTime = time.Unix(0, 0)
|
|
|
|
}
|
2022-08-04 17:19:05 +00:00
|
|
|
var metadata fs.Metadata
|
|
|
|
if f.metaRules.len() > 0 {
|
|
|
|
var err error
|
|
|
|
metadata, err = fs.GetMetadata(ctx, o)
|
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(o, "Failed to read metadata: %v", err)
|
|
|
|
metadata = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return f.Include(o.Remote(), o.Size(), modTime, metadata)
|
2015-12-17 11:52:38 +00:00
|
|
|
}
|
|
|
|
|
2015-09-27 15:13:20 +00:00
|
|
|
// DumpFilters dumps the filters in textual form, 1 per line
|
|
|
|
func (f *Filter) DumpFilters() string {
|
|
|
|
rules := []string{}
|
2015-12-17 11:52:38 +00:00
|
|
|
if !f.ModTimeFrom.IsZero() {
|
|
|
|
rules = append(rules, fmt.Sprintf("Last-modified date must be equal or greater than: %s", f.ModTimeFrom.String()))
|
|
|
|
}
|
|
|
|
if !f.ModTimeTo.IsZero() {
|
|
|
|
rules = append(rules, fmt.Sprintf("Last-modified date must be equal or less than: %s", f.ModTimeTo.String()))
|
|
|
|
}
|
2016-05-16 16:14:04 +00:00
|
|
|
rules = append(rules, "--- File filter rules ---")
|
|
|
|
for _, rule := range f.fileRules.rules {
|
2015-09-27 15:13:20 +00:00
|
|
|
rules = append(rules, rule.String())
|
|
|
|
}
|
2016-05-16 16:14:04 +00:00
|
|
|
rules = append(rules, "--- Directory filter rules ---")
|
|
|
|
for _, dirRule := range f.dirRules.rules {
|
|
|
|
rules = append(rules, dirRule.String())
|
|
|
|
}
|
2022-08-04 17:19:05 +00:00
|
|
|
if f.metaRules.len() > 0 {
|
|
|
|
rules = append(rules, "--- Metadata filter rules ---")
|
|
|
|
for _, metaRule := range f.metaRules.rules {
|
|
|
|
rules = append(rules, metaRule.String())
|
|
|
|
}
|
|
|
|
}
|
2015-09-27 15:13:20 +00:00
|
|
|
return strings.Join(rules, "\n")
|
|
|
|
}
|
2018-10-19 16:41:14 +00:00
|
|
|
|
|
|
|
// HaveFilesFrom returns true if --files-from has been supplied
|
|
|
|
func (f *Filter) HaveFilesFrom() bool {
|
|
|
|
return f.files != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var errFilesFromNotSet = errors.New("--files-from not set so can't use Filter.ListR")
|
|
|
|
|
|
|
|
// MakeListR makes function to return all the files set using --files-from
|
2019-06-17 08:34:30 +00:00
|
|
|
func (f *Filter) MakeListR(ctx context.Context, NewObject func(ctx context.Context, remote string) (fs.Object, error)) fs.ListRFn {
|
|
|
|
return func(ctx context.Context, dir string, callback fs.ListRCallback) error {
|
2020-11-05 11:33:32 +00:00
|
|
|
ci := fs.GetConfig(ctx)
|
2018-10-19 16:41:14 +00:00
|
|
|
if !f.HaveFilesFrom() {
|
|
|
|
return errFilesFromNotSet
|
|
|
|
}
|
2018-12-13 10:47:09 +00:00
|
|
|
var (
|
2020-11-05 11:33:32 +00:00
|
|
|
checkers = ci.Checkers
|
|
|
|
remotes = make(chan string, checkers)
|
2023-06-10 14:53:08 +00:00
|
|
|
g, gCtx = errgroup.WithContext(ctx)
|
2018-12-13 10:47:09 +00:00
|
|
|
)
|
2020-11-05 11:33:32 +00:00
|
|
|
for i := 0; i < checkers; i++ {
|
2018-12-13 10:47:09 +00:00
|
|
|
g.Go(func() (err error) {
|
|
|
|
var entries = make(fs.DirEntries, 1)
|
|
|
|
for remote := range remotes {
|
2023-06-10 14:53:08 +00:00
|
|
|
entries[0], err = NewObject(gCtx, remote)
|
2018-12-13 10:47:09 +00:00
|
|
|
if err == fs.ErrorObjectNotFound {
|
|
|
|
// Skip files that are not found
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
err = callback(entries)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
2023-06-10 14:53:08 +00:00
|
|
|
outer:
|
2018-10-19 16:41:14 +00:00
|
|
|
for remote := range f.files {
|
2023-06-10 14:53:08 +00:00
|
|
|
select {
|
|
|
|
case remotes <- remote:
|
|
|
|
case <-gCtx.Done():
|
|
|
|
break outer
|
|
|
|
}
|
2018-10-19 16:41:14 +00:00
|
|
|
}
|
2018-12-13 10:47:09 +00:00
|
|
|
close(remotes)
|
|
|
|
return g.Wait()
|
2018-10-19 16:41:14 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-08 12:58:48 +00:00
|
|
|
|
|
|
|
// UsesDirectoryFilters returns true if the filter uses directory
|
|
|
|
// filters and false if it doesn't.
|
|
|
|
//
|
|
|
|
// This is used in deciding whether to walk directories or use ListR
|
|
|
|
func (f *Filter) UsesDirectoryFilters() bool {
|
|
|
|
if len(f.dirRules.rules) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
rule := f.dirRules.rules[0]
|
|
|
|
re := rule.Regexp.String()
|
2022-06-08 20:25:17 +00:00
|
|
|
if rule.Include && re == "^.*$" {
|
2019-08-08 12:58:48 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
2020-11-26 17:10:41 +00:00
|
|
|
|
2020-12-02 16:20:58 +00:00
|
|
|
// Context key for config
|
2020-11-26 17:10:41 +00:00
|
|
|
type configContextKeyType struct{}
|
|
|
|
|
|
|
|
var configContextKey = configContextKeyType{}
|
|
|
|
|
|
|
|
// GetConfig returns the global or context sensitive config
|
|
|
|
func GetConfig(ctx context.Context) *Filter {
|
|
|
|
if ctx == nil {
|
|
|
|
return globalConfig
|
|
|
|
}
|
|
|
|
c := ctx.Value(configContextKey)
|
|
|
|
if c == nil {
|
|
|
|
return globalConfig
|
|
|
|
}
|
|
|
|
return c.(*Filter)
|
|
|
|
}
|
|
|
|
|
2021-02-19 13:13:54 +00:00
|
|
|
// CopyConfig copies the global config (if any) from srcCtx into
|
|
|
|
// dstCtx returning the new context.
|
|
|
|
func CopyConfig(dstCtx, srcCtx context.Context) context.Context {
|
|
|
|
if srcCtx == nil {
|
|
|
|
return dstCtx
|
|
|
|
}
|
|
|
|
c := srcCtx.Value(configContextKey)
|
|
|
|
if c == nil {
|
|
|
|
return dstCtx
|
|
|
|
}
|
|
|
|
return context.WithValue(dstCtx, configContextKey, c)
|
|
|
|
}
|
|
|
|
|
2020-11-26 17:10:41 +00:00
|
|
|
// AddConfig returns a mutable config structure based on a shallow
|
|
|
|
// copy of that found in ctx and returns a new context with that added
|
|
|
|
// to it.
|
|
|
|
func AddConfig(ctx context.Context) (context.Context, *Filter) {
|
|
|
|
c := GetConfig(ctx)
|
|
|
|
cCopy := new(Filter)
|
|
|
|
*cCopy = *c
|
|
|
|
newCtx := context.WithValue(ctx, configContextKey, cCopy)
|
|
|
|
return newCtx, cCopy
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReplaceConfig replaces the filter config in the ctx with the one
|
|
|
|
// passed in and returns a new context with that added to it.
|
|
|
|
func ReplaceConfig(ctx context.Context, f *Filter) context.Context {
|
|
|
|
newCtx := context.WithValue(ctx, configContextKey, f)
|
|
|
|
return newCtx
|
|
|
|
}
|
2020-12-02 16:20:58 +00:00
|
|
|
|
|
|
|
// Context key for the "use filter" flag
|
|
|
|
type useFlagContextKeyType struct{}
|
|
|
|
|
|
|
|
var useFlagContextKey = useFlagContextKeyType{}
|
|
|
|
|
|
|
|
// GetUseFilter obtains the "use filter" flag from context
|
|
|
|
// The flag tells filter-aware backends (Drive) to constrain List using filter
|
|
|
|
func GetUseFilter(ctx context.Context) bool {
|
|
|
|
if ctx != nil {
|
|
|
|
if pVal := ctx.Value(useFlagContextKey); pVal != nil {
|
|
|
|
return *(pVal.(*bool))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetUseFilter returns a context having (re)set the "use filter" flag
|
|
|
|
func SetUseFilter(ctx context.Context, useFilter bool) context.Context {
|
|
|
|
if useFilter == GetUseFilter(ctx) {
|
|
|
|
return ctx // Minimize depth of nested contexts
|
|
|
|
}
|
|
|
|
pVal := new(bool)
|
|
|
|
*pVal = useFilter
|
|
|
|
return context.WithValue(ctx, useFlagContextKey, pVal)
|
|
|
|
}
|
2024-07-04 16:29:50 +00:00
|
|
|
|
|
|
|
// Reload the filters from the flags
|
|
|
|
func Reload(ctx context.Context) (err error) {
|
|
|
|
fi := GetConfig(ctx)
|
|
|
|
newFilter, err := NewFilter(&Opt)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*fi = *newFilter
|
|
|
|
return nil
|
|
|
|
}
|