package filesystem import ( "context" "encoding/json" "log/slog" "strings" "time" "github.com/valkey-io/valkey-go" ) const keyRead = "webdav:read:" type readFile struct { Age time.Time `json:"age"` Content []byte `json:"content"` } func (c *CachedClient) Read(path string) ([]byte, error) { key := keyRead + path ctx := context.Background() // Try cache cached, err := c.client.Do(ctx, c.client.B().Get().Key(key).Build()).AsBytes() if err == nil && len(cached) > 0 { c.readHits.Add(1) c.hits.Add(1) slog.Debug("cache hit", "key", key) var file readFile err := json.Unmarshal(cached, &file) if err != nil { slog.Error("could not Unmarshal cache", "key", key, "err", err) } else { return file.Content, nil } } // Cache miss c.misses.Add(1) slog.Debug("cache miss", "key", key) data, err := c.impl.Read(path) if err != nil { return nil, err } file := readFile{ Age: time.Now(), Content: data, } cached, err = json.Marshal(file) if err != nil { slog.Error("could not marshal file for cache", "key", key, "err", err) return data, nil } // Cache c.client.Do(ctx, c.client.B().Set().Key(key).Value(valkey.BinaryString(cached)).Ex(c.ttl).Build()) return data, nil } func (c *CachedClient) revalidateRead(ctx context.Context) { startTime := time.Now() c.scanAndProcess(ctx, keyRead+"*", func(key string) error { path := strings.TrimPrefix(key, keyRead) cached, err := c.client.Do(ctx, c.client.B().Get().Key(key).Build()).AsBytes() if err != nil { // Key might be gone or error fetching return nil } var cachedFile readFile err = json.Unmarshal(cached, &cachedFile) if err != nil { c.client.Do(ctx, c.client.B().Del().Key(key).Build()) return err } info, err := c.impl.Stat(path) if err != nil { c.client.Do(ctx, c.client.B().Del().Key(key).Build()) return nil } if info.ModTime().After(cachedFile.Age) { data, err := c.impl.Read(path) if err != nil { return err } cachedFile.Age = time.Now() cachedFile.Content = data newCached, err := json.Marshal(cachedFile) if err != nil { return err } c.client.Do(ctx, c.client.B().Set().Key(key).Value(valkey.BinaryString(newCached)).Ex(c.ttl).Build()) } else { c.client.Do(ctx, c.client.B().Expire().Key(key).Seconds(int64(c.ttl/time.Second)).Build()) } return nil }) slog.Debug("revalidation of read complete", "duration", time.Since(startTime)) }