// Copyright (C) 2022 Marius Schellenberger package fs import ( "errors" "io" "os" "sync" "time" "github.com/go-logr/logr" ) var NoCacheEntry = errors.New("no cache entry") type DirCacheEntry struct { mtime time.Time fi []os.FileInfo } type DirCache struct { mu sync.RWMutex m map[string]*DirCacheEntry } func NewDirCache() *DirCache { return &DirCache{m: make(map[string]*DirCacheEntry)} } func (dc *DirCache) Get(name string) (*DirCacheEntry, error) { dc.mu.RLock() defer dc.mu.RUnlock() if dce, ok := dc.m[name]; ok { return dce, nil } return nil, NoCacheEntry } func (dc *DirCache) Set(name string, dce *DirCacheEntry) { dc.mu.Lock() defer dc.mu.Unlock() dc.m[name] = dce } type Dir struct { log logr.Logger f *os.File dc *DirCache } func (f *Dir) Read(_ []byte) (int, error) { return 0, io.EOF } func (f *Dir) Seek(_ int64, _ int) (int64, error) { return 0, io.EOF } func readDir(f *os.File) (fi []os.FileInfo, err error) { ent, err := f.ReadDir(0) if err != nil { return } fi = make([]os.FileInfo, len(ent)) for j, e := range ent { i, _ := e.Info() fi[j] = i } return } func (f *Dir) Readdir(_ int) (fi []os.FileInfo, err error) { i, err := f.Stat() if err != nil { return } name := f.f.Name() dce, err := f.dc.Get(name) log := f.log if err == NoCacheEntry || i.ModTime().After(dce.mtime) { log.V(3).Info("cache miss") fi, err = readDir(f.f) if err != nil { return } dce = &DirCacheEntry{i.ModTime(), fi} f.dc.Set(name, dce) } else { log.V(3).Info("cache hit") } fi = dce.fi return } func (f *Dir) Stat() (os.FileInfo, error) { return f.f.Stat() } func (f *Dir) Close() error { return f.f.Close() }