v1.10.0
ADDED: * paths.SearchFsPaths, which lets a user provide a fairly flexible function for searching files/directories/etc.
This commit is contained in:
176
paths/funcs.go
176
paths/funcs.go
@@ -25,8 +25,15 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
`regexp`
|
||||
`slices`
|
||||
"strings"
|
||||
`time`
|
||||
|
||||
// "syscall"
|
||||
|
||||
`github.com/djherbis/times`
|
||||
`r00t2.io/goutils/bitmask`
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -266,3 +273,172 @@ func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SearchPaths gets a file/directory path list based on the provided criteria.
|
||||
|
||||
targetType defines what should be included in the path list.
|
||||
It can consist of one or more (io/)fs.FileMode types OR'd together
|
||||
(ensure they are part of (io/)fs.ModeType).
|
||||
(You can use 0 as a shortcut to match anything/all paths.
|
||||
You can also use (io/)fs.ModeType itself to match anything/all paths.)
|
||||
|
||||
basePtrn may be nil; if it isn't, it will be applied to *base names*
|
||||
(that is, quux.txt rather than /foo/bar/baz/quux.txt).
|
||||
|
||||
pathPtrn is like ptrn except it applies to the *entire* path,
|
||||
not just the basename, if not nil (e.g. /foo/bar/baz/quux.txt,
|
||||
not just quux.txt).
|
||||
|
||||
If age is not nil, it will be applied to the path object.
|
||||
It will match older files/directories/etc. if olderThan is true,
|
||||
otherwise it will match newer files/directories/etc.
|
||||
(olderThan is not used otherwise.)
|
||||
|
||||
ageType is one or more Time* constants OR'd together to describe which timestamp type to check.
|
||||
(Note that TimeCreated may not match if specified as it is only available on certain OSes,
|
||||
kernel versions, and filesystems. This would lead to files being excluded that may have otherwise
|
||||
been included.)
|
||||
(You can use TimeAny to specify any supported time.)
|
||||
*Any* matching timestamp of all specified (and supported) timestamp types matches,
|
||||
so be judicious with your selection.
|
||||
|
||||
olderThan (as mentioned above) will find paths *older* than age if true, otherwise *newer*.
|
||||
*/
|
||||
func SearchFsPaths(
|
||||
root string,
|
||||
targetType fs.FileMode,
|
||||
basePtrn, pathPtrn *regexp.Regexp,
|
||||
age *time.Duration, ageType pathTimeType, olderThan bool,
|
||||
) (foundPaths []string, err error) {
|
||||
|
||||
var now time.Time = time.Now()
|
||||
|
||||
if err = RealPath(&root); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = filepath.WalkDir(
|
||||
root,
|
||||
func(path string, d fs.DirEntry, inErr error) (outErr error) {
|
||||
|
||||
var typeMode fs.FileMode
|
||||
var fi fs.FileInfo
|
||||
var tspec times.Timespec
|
||||
var typeFilter *bitmask.MaskBit = bitmask.NewMaskBitExplicit(uint(targetType))
|
||||
|
||||
if inErr != nil {
|
||||
outErr = inErr
|
||||
return
|
||||
}
|
||||
|
||||
// patterns
|
||||
if pathPtrn != nil {
|
||||
if !pathPtrn.MatchString(path) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if basePtrn != nil {
|
||||
if !basePtrn.MatchString(filepath.Base(path)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// age
|
||||
if age != nil {
|
||||
if tspec, outErr = times.Stat(path); outErr != nil {
|
||||
return
|
||||
}
|
||||
if !filterTimes(tspec, age, &ageType, olderThan, &now) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// fs object type (file, dir, etc.)
|
||||
if targetType != 0 && uint(targetType) != uint(modeAny) {
|
||||
if fi, outErr = d.Info(); outErr != nil {
|
||||
return
|
||||
}
|
||||
typeMode = fi.Mode().Type()
|
||||
if !typeFilter.HasFlag(bitmask.MaskBit(typeMode)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// All filters passed at this point.
|
||||
foundPaths = append(foundPaths, path)
|
||||
|
||||
return
|
||||
},
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// And sort them.
|
||||
slices.Sort(foundPaths)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
filterTimes checks a times.Timespec of a file using:
|
||||
* an age specified by the caller
|
||||
* an ageType bitmask for types of times to compare
|
||||
* an olderThan bool (if false, the file must be younger than)
|
||||
* an optional "now" timestamp for the age derivation.
|
||||
*/
|
||||
func filterTimes(tspec times.Timespec, age *time.Duration, ageType *pathTimeType, olderThan bool, now *time.Time) (include bool) {
|
||||
|
||||
var curAge time.Duration
|
||||
var mask *bitmask.MaskBit
|
||||
var tfunc func(t *time.Duration) (match bool) = func(t *time.Duration) (match bool) {
|
||||
if olderThan {
|
||||
match = *t > *age
|
||||
} else {
|
||||
match = *t < *age
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if tspec == nil || age == nil || ageType == nil {
|
||||
return
|
||||
}
|
||||
|
||||
mask = ageType.Mask()
|
||||
|
||||
if now == nil {
|
||||
now = new(time.Time)
|
||||
*now = time.Now()
|
||||
}
|
||||
|
||||
// ATIME
|
||||
if mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeAccessed)) {
|
||||
curAge = now.Sub(tspec.AccessTime())
|
||||
if include = tfunc(&curAge); include {
|
||||
return
|
||||
}
|
||||
}
|
||||
// MTIME
|
||||
if mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeModified)) {
|
||||
curAge = now.Sub(tspec.ModTime())
|
||||
if include = tfunc(&curAge); include {
|
||||
return
|
||||
}
|
||||
}
|
||||
// CTIME (if supported)
|
||||
if tspec.HasChangeTime() && (mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeChanged))) {
|
||||
curAge = now.Sub(tspec.ChangeTime())
|
||||
if include = tfunc(&curAge); include {
|
||||
return
|
||||
}
|
||||
}
|
||||
// BTIME (if supported)
|
||||
if tspec.HasBirthTime() && (mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeCreated))) {
|
||||
curAge = now.Sub(tspec.BirthTime())
|
||||
if include = tfunc(&curAge); include {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user