tstor/src/vfs/utils.go
royalcat 95016d54c1
Some checks failed
docker / build-docker (push) Failing after 1m49s
fix walk
2024-12-05 13:58:07 +03:00

156 lines
3.4 KiB
Go

package vfs
import (
"context"
"io/fs"
"path"
"path/filepath"
"strings"
"sync"
"time"
)
const Separator = "/"
func IsRoot(filename string) bool {
return path.Clean(filename) == Separator
}
func trimRelPath(p, t string) string {
return strings.Trim(strings.TrimPrefix(p, t), "/")
}
// func clean(p string) string {
// return path.Clean(Separator + strings.ReplaceAll(p, "\\", "/"))
// }
func RelPath(p string) string {
return strings.TrimLeft(p, Separator)
}
func AbsPath(p string) string {
if p == "" || p[0] != '/' {
return Separator + p
}
return p
}
func AddTrailSlash(p string) string {
if p == "" || p[len(p)-1] != '/' {
return p + Separator
}
return p
}
func RemoveTrailingSlash(p string) string {
if p == Separator {
return ""
}
return strings.TrimSuffix(p, Separator)
}
// OnceValueWOErr returns a function that invokes f only once and returns the value
// returned by f . The returned function may be called concurrently.
//
// If f panics, the returned function will panic with the same value on every call.
func OnceValueWOErr[T any](f func() (T, error)) func() (T, error) {
var (
mu sync.Mutex
isExecuted bool
r1 T
err error
)
return func() (T, error) {
mu.Lock()
defer mu.Unlock()
if isExecuted && err == nil {
return r1, nil
}
r1, err = f()
return r1, err
}
}
func subTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
if deadline, ok := ctx.Deadline(); ok {
timeout := time.Until(deadline) / 2
return context.WithTimeout(ctx, timeout)
}
return ctx, func() {}
}
func Walk(ctx context.Context, vfs Filesystem, root string, walkFn filepath.WalkFunc) error {
info, err := vfs.Stat(ctx, root)
if err != nil {
err = walkFn(root, nil, err)
} else {
err = walk(ctx, vfs, root, info, walkFn)
}
if err == filepath.SkipDir {
return nil
}
return err
}
// walk recursively descends path, calling walkFn
// adapted from https://golang.org/src/path/filepath/path.go
func walk(ctx context.Context, vfs Filesystem, path string, info fs.FileInfo, walkFn filepath.WalkFunc) error {
if !info.IsDir() {
return walkFn(path, info, nil)
}
names, err := readdirnames(ctx, vfs, path)
err1 := walkFn(path, info, err)
// If err != nil, walk can't walk into this directory.
// err1 != nil means walkFn want walk to skip this directory or stop walking.
// Therefore, if one of err and err1 isn't nil, walk will return.
if err != nil || err1 != nil {
// The caller's behavior is controlled by the return value, which is decided
// by walkFn. walkFn may ignore err and return nil.
// If walkFn returns SkipDir, it will be handled by the caller.
// So walk should return whatever walkFn returns.
return err1
}
for _, name := range names {
filename := filepath.Join(path, name)
fileInfo, err := vfs.Stat(ctx, filename)
if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
return err
}
} else {
err = walk(ctx, vfs, filename, fileInfo, walkFn)
if err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
}
return nil
}
func readdirnames(ctx context.Context, vfs Filesystem, dir string) ([]string, error) {
files, err := vfs.ReadDir(ctx, dir)
if err != nil {
return nil, err
}
names := make([]string, 0, len(files))
for _, file := range files {
if file == nil {
continue
}
names = append(names, file.Name())
}
return names, nil
}