tstor/src/vfs/utils.go

157 lines
3.4 KiB
Go
Raw Normal View History

2023-10-16 09:18:40 +00:00
package vfs
2024-01-28 20:22:49 +00:00
import (
2024-04-06 13:51:36 +00:00
"context"
2024-06-14 22:14:44 +00:00
"io/fs"
2024-03-28 13:09:42 +00:00
"path"
2024-06-14 22:14:44 +00:00
"path/filepath"
2024-01-28 20:22:49 +00:00
"strings"
"sync"
2024-04-06 13:51:36 +00:00
"time"
2024-01-28 20:22:49 +00:00
)
2023-10-16 09:18:40 +00:00
2024-03-28 13:09:42 +00:00
const Separator = "/"
2024-05-19 21:24:09 +00:00
func IsRoot(filename string) bool {
2024-03-28 13:09:42 +00:00
return path.Clean(filename) == Separator
}
2023-10-16 09:18:40 +00:00
func trimRelPath(p, t string) string {
return strings.Trim(strings.TrimPrefix(p, t), "/")
}
2023-12-21 23:15:39 +00:00
// func clean(p string) string {
// return path.Clean(Separator + strings.ReplaceAll(p, "\\", "/"))
// }
2024-09-24 13:26:15 +00:00
func RelPath(p string) string {
return strings.TrimLeft(p, Separator)
}
2023-12-21 23:15:39 +00:00
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
2023-10-16 09:18:40 +00:00
}
2024-01-28 20:22:49 +00:00
2024-11-15 13:39:56 +00:00
func RemoveTrailingSlash(p string) string {
if p == Separator {
return ""
}
return strings.TrimSuffix(p, Separator)
}
2024-01-28 20:22:49 +00:00
// 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
}
}
2024-04-06 13:51:36 +00:00
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() {}
}
2024-06-14 22:14:44 +00:00
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
}
2024-12-05 10:58:07 +00:00
names := make([]string, 0, len(files))
2024-06-14 22:14:44 +00:00
for _, file := range files {
2024-12-05 10:58:07 +00:00
if file == nil {
continue
}
2024-06-14 22:14:44 +00:00
names = append(names, file.Name())
}
return names, nil
}