AdGuardHome/internal/aghos/filewalker.go
Ainar Garipov 5cc05e2c4b Pull request 2187: upd-golibs
Squashed commit of the following:

commit 63c14cf0eb395f58149f5a82ff1389353f7f8127
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 2 20:10:10 2024 +0300

    all: imp code, docs

commit 185ccdd1d9f5acc8376fabeac647f6fddcf108b5
Merge: b6ca80a9f d4fff41b3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 2 20:04:23 2024 +0300

    Merge branch 'master' into upd-golibs

commit b6ca80a9f639394758cc9000345c132a713c183c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 2 20:01:10 2024 +0300

    all: upd to tags

commit 474f62319befbe22cf1bccd2320cd0d3da1629b1
Author: Ainar Garipov <a.garipov@adguard.com>
Date:   Tue Mar 26 16:33:45 2024 +0300

    all: upd golibs
2024-04-03 13:44:51 +03:00

123 lines
3.1 KiB
Go

package aghos
import (
"fmt"
"io"
"io/fs"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
)
// FileWalker is the signature of a function called for files in the file tree.
// As opposed to filepath.Walk it only walk the files (not directories) matching
// the provided pattern and those returned by function itself. All patterns
// should be valid for fs.Glob. If FileWalker returns false for cont then
// walking terminates. Prefer using bufio.Scanner to read the r since the input
// is not limited.
//
// TODO(e.burkov, a.garipov): Move into another package like aghfs.
//
// TODO(e.burkov): Think about passing filename or any additional data.
type FileWalker func(r io.Reader) (patterns []string, cont bool, err error)
// checkFile tries to open and process a single file located on sourcePath in
// the specified fsys. The path is skipped if it's a directory.
func checkFile(
fsys fs.FS,
c FileWalker,
sourcePath string,
) (patterns []string, cont bool, err error) {
var f fs.File
f, err = fsys.Open(sourcePath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
// Ignore non-existing files since this may only happen when the
// file was removed after filepath.Glob matched it.
return nil, true, nil
}
return nil, false, err
}
defer func() { err = errors.WithDeferred(err, f.Close()) }()
var fi fs.FileInfo
if fi, err = f.Stat(); err != nil {
return nil, true, err
} else if fi.IsDir() {
// Skip the directories.
return nil, true, nil
}
return c(f)
}
// handlePatterns parses the patterns in fsys and ignores duplicates using
// srcSet. srcSet must be non-nil.
func handlePatterns(
fsys fs.FS,
srcSet *container.MapSet[string],
patterns ...string,
) (sub []string, err error) {
sub = make([]string, 0, len(patterns))
for _, p := range patterns {
var matches []string
matches, err = fs.Glob(fsys, p)
if err != nil {
// Enrich error with the pattern because filepath.Glob
// doesn't do it.
return nil, fmt.Errorf("invalid pattern %q: %w", p, err)
}
for _, m := range matches {
if srcSet.Has(m) {
continue
}
srcSet.Add(m)
sub = append(sub, m)
}
}
return sub, nil
}
// Walk starts walking the files in fsys defined by patterns from initial.
// It only returns true if fw signed to stop walking.
func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
// The slice of sources keeps the order in which the files are walked since
// srcSet.Values() returns strings in undefined order.
srcSet := container.NewMapSet[string]()
var src []string
src, err = handlePatterns(fsys, srcSet, initial...)
if err != nil {
return false, err
}
var filename string
defer func() { err = errors.Annotate(err, "checking %q: %w", filename) }()
for i := 0; i < len(src); i++ {
var patterns []string
var cont bool
filename = src[i]
patterns, cont, err = checkFile(fsys, fw, src[i])
if err != nil {
return false, err
}
if !cont {
return true, nil
}
var subsrc []string
subsrc, err = handlePatterns(fsys, srcSet, patterns...)
if err != nil {
return false, err
}
src = append(src, subsrc...)
}
return false, nil
}