Files
thefeed/internal/client/cache.go
T
2026-03-25 11:30:02 +03:30

123 lines
2.5 KiB
Go

package client
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/sartoopjj/thefeed/internal/protocol"
)
// Cache provides file-based caching for channel data.
type Cache struct {
dir string
mu sync.RWMutex
}
type cachedChannel struct {
Messages []protocol.Message `json:"messages"`
FetchedAt int64 `json:"fetched_at"`
}
type cachedMeta struct {
Metadata *protocol.Metadata `json:"metadata"`
FetchedAt int64 `json:"fetched_at"`
}
// NewCache creates a file cache in the given directory.
func NewCache(dir string) (*Cache, error) {
if err := os.MkdirAll(dir, 0700); err != nil {
return nil, fmt.Errorf("create cache dir: %w", err)
}
return &Cache{dir: dir}, nil
}
// GetMessages returns cached messages for a channel, or nil if expired.
func (c *Cache) GetMessages(channelNum int, maxAge time.Duration) []protocol.Message {
c.mu.RLock()
defer c.mu.RUnlock()
path := c.channelPath(channelNum)
data, err := os.ReadFile(path)
if err != nil {
return nil
}
var cached cachedChannel
if err := json.Unmarshal(data, &cached); err != nil {
return nil
}
if maxAge > 0 && time.Since(time.Unix(cached.FetchedAt, 0)) > maxAge {
return nil
}
return cached.Messages
}
// PutMessages stores messages for a channel.
func (c *Cache) PutMessages(channelNum int, msgs []protocol.Message) error {
c.mu.Lock()
defer c.mu.Unlock()
cached := cachedChannel{
Messages: msgs,
FetchedAt: time.Now().Unix(),
}
data, err := json.Marshal(cached)
if err != nil {
return err
}
return os.WriteFile(c.channelPath(channelNum), data, 0600)
}
// GetMetadata returns cached metadata, or nil if expired.
func (c *Cache) GetMetadata(maxAge time.Duration) *protocol.Metadata {
c.mu.RLock()
defer c.mu.RUnlock()
path := filepath.Join(c.dir, "metadata.json")
data, err := os.ReadFile(path)
if err != nil {
return nil
}
var cached cachedMeta
if err := json.Unmarshal(data, &cached); err != nil {
return nil
}
if maxAge > 0 && time.Since(time.Unix(cached.FetchedAt, 0)) > maxAge {
return nil
}
return cached.Metadata
}
// PutMetadata stores metadata.
func (c *Cache) PutMetadata(meta *protocol.Metadata) error {
c.mu.Lock()
defer c.mu.Unlock()
cached := cachedMeta{
Metadata: meta,
FetchedAt: time.Now().Unix(),
}
data, err := json.Marshal(cached)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(c.dir, "metadata.json"), data, 0600)
}
func (c *Cache) channelPath(channelNum int) string {
return filepath.Join(c.dir, fmt.Sprintf("channel_%d.json", channelNum))
}