110 lines
2.4 KiB
Go
110 lines
2.4 KiB
Go
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))
|
|
}
|