mirror of
https://github.com/sartoopjj/thefeed.git
synced 2026-05-18 07:44:45 +03:00
123 lines
2.5 KiB
Go
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))
|
|
}
|