@@ -0,0 +1,109 @@
|
||||
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.cache.Do(ctx, c.cache.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.cache.Do(ctx, c.cache.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.cache.Do(ctx, c.cache.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.cache.Do(ctx, c.cache.B().Del().Key(key).Build())
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := c.impl.Stat(path)
|
||||
if err != nil {
|
||||
c.cache.Do(ctx, c.cache.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.cache.Do(ctx, c.cache.B().Set().Key(key).Value(valkey.BinaryString(newCached)).Ex(c.ttl).Build())
|
||||
} else {
|
||||
c.cache.Do(ctx, c.cache.B().Expire().Key(key).Seconds(int64(c.ttl/time.Second)).Build())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
slog.Debug("revalidation of read complete", "duration", time.Since(startTime))
|
||||
}
|
||||
Reference in New Issue
Block a user