package filesystem import ( "context" "log/slog" "sync/atomic" "time" "github.com/valkey-io/valkey-go" ) const keyStat = "webdav:stat:" // CachedClient wraps a FileSystem implementation with Valkey caching. type CachedClient struct { impl FS cache valkey.Client ttl time.Duration hits atomic.Int64 misses atomic.Int64 readDirHits atomic.Int64 readHits atomic.Int64 } // NewCachedClient creates a new CachedClient. func NewCachedClient(impl FS, client valkey.Client, refreshtime time.Duration) *CachedClient { c := &CachedClient{ impl: impl, cache: client, ttl: refreshtime * 5, } go func() { t := time.NewTicker(refreshtime) for { <-t.C c.revalidate(context.Background()) } }() return c } func (c *CachedClient) revalidate(ctx context.Context) { slog.Info("starting revalidation") go c.revalidateReadDir(ctx) go c.revalidateRead(ctx) go c.revalidateStat(ctx) } func (c *CachedClient) scanAndProcess(ctx context.Context, match string, process func(key string) error) { cursor := uint64(0) for { res, err := c.cache.Do(ctx, c.cache.B().Scan().Cursor(cursor).Match(match).Build()).AsScanEntry() if err != nil { slog.Error("scan failed", "match", match, "err", err) return } for _, key := range res.Elements { if err := process(key); err != nil { slog.Debug("process failed", "key", key, "err", err) } } cursor = res.Cursor if cursor == 0 { break } } }