Implementing a streaming setup with Usenet
This commit is contained in:
141
pkg/usenet/cache.go
Normal file
141
pkg/usenet/cache.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package usenet
|
||||
|
||||
import (
|
||||
"github.com/chrisfarms/yenc"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SegmentCache provides intelligent caching for NNTP segments
|
||||
type SegmentCache struct {
|
||||
cache *xsync.Map[string, *CachedSegment]
|
||||
logger zerolog.Logger
|
||||
maxSize int64
|
||||
currentSize atomic.Int64
|
||||
}
|
||||
|
||||
// CachedSegment represents a cached segment with metadata
|
||||
type CachedSegment struct {
|
||||
MessageID string `json:"message_id"`
|
||||
Data []byte `json:"data"`
|
||||
DecodedSize int64 `json:"decoded_size"` // Actual size after yEnc decoding
|
||||
DeclaredSize int64 `json:"declared_size"` // Size declared in NZB
|
||||
CachedAt time.Time `json:"cached_at"`
|
||||
AccessCount int64 `json:"access_count"`
|
||||
LastAccess time.Time `json:"last_access"`
|
||||
FileBegin int64 `json:"file_begin"` // Start byte offset in the file
|
||||
FileEnd int64 `json:"file_end"` // End byte offset in the file
|
||||
}
|
||||
|
||||
// NewSegmentCache creates a new segment cache
|
||||
func NewSegmentCache(logger zerolog.Logger) *SegmentCache {
|
||||
sc := &SegmentCache{
|
||||
cache: xsync.NewMap[string, *CachedSegment](),
|
||||
logger: logger.With().Str("component", "segment_cache").Logger(),
|
||||
maxSize: 50 * 1024 * 1024, // Default max size 100MB
|
||||
}
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
// Get retrieves a segment from cache
|
||||
func (sc *SegmentCache) Get(messageID string) (*CachedSegment, bool) {
|
||||
segment, found := sc.cache.Load(messageID)
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
segment.AccessCount++
|
||||
segment.LastAccess = time.Now()
|
||||
|
||||
return segment, true
|
||||
}
|
||||
|
||||
// Put stores a segment in cache with intelligent size management
|
||||
func (sc *SegmentCache) Put(messageID string, data *yenc.Part, declaredSize int64) {
|
||||
dataSize := data.Size
|
||||
|
||||
currentSize := sc.currentSize.Load()
|
||||
// Check if we need to make room
|
||||
wouldExceed := (currentSize + dataSize) > sc.maxSize
|
||||
|
||||
if wouldExceed {
|
||||
sc.evictLRU(dataSize)
|
||||
}
|
||||
|
||||
segment := &CachedSegment{
|
||||
MessageID: messageID,
|
||||
Data: make([]byte, data.Size),
|
||||
DecodedSize: dataSize,
|
||||
DeclaredSize: declaredSize,
|
||||
CachedAt: time.Now(),
|
||||
AccessCount: 1,
|
||||
LastAccess: time.Now(),
|
||||
}
|
||||
|
||||
copy(segment.Data, data.Body)
|
||||
|
||||
sc.cache.Store(messageID, segment)
|
||||
|
||||
sc.currentSize.Add(dataSize)
|
||||
}
|
||||
|
||||
// evictLRU evicts least recently used segments to make room
|
||||
func (sc *SegmentCache) evictLRU(neededSpace int64) {
|
||||
if neededSpace <= 0 {
|
||||
return // No need to evict if no space is needed
|
||||
}
|
||||
if sc.cache.Size() == 0 {
|
||||
return // Nothing to evict
|
||||
}
|
||||
|
||||
// Create a sorted list of segments by last access time
|
||||
type segmentInfo struct {
|
||||
key string
|
||||
segment *CachedSegment
|
||||
lastAccess time.Time
|
||||
}
|
||||
|
||||
segments := make([]segmentInfo, 0, sc.cache.Size())
|
||||
sc.cache.Range(func(key string, value *CachedSegment) bool {
|
||||
segments = append(segments, segmentInfo{
|
||||
key: key,
|
||||
segment: value,
|
||||
lastAccess: value.LastAccess,
|
||||
})
|
||||
return true // continue iteration
|
||||
})
|
||||
|
||||
// Sort by last access time (oldest first)
|
||||
for i := 0; i < len(segments)-1; i++ {
|
||||
for j := i + 1; j < len(segments); j++ {
|
||||
if segments[i].lastAccess.After(segments[j].lastAccess) {
|
||||
segments[i], segments[j] = segments[j], segments[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evict segments until we have enough space
|
||||
freedSpace := int64(0)
|
||||
for _, seg := range segments {
|
||||
if freedSpace >= neededSpace {
|
||||
break
|
||||
}
|
||||
|
||||
sc.cache.Delete(seg.key)
|
||||
freedSpace += int64(len(seg.segment.Data))
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all cached segments
|
||||
func (sc *SegmentCache) Clear() {
|
||||
sc.cache.Clear()
|
||||
sc.currentSize.Store(0)
|
||||
}
|
||||
|
||||
// Delete removes a specific segment from cache
|
||||
func (sc *SegmentCache) Delete(messageID string) {
|
||||
sc.cache.Delete(messageID)
|
||||
}
|
||||
Reference in New Issue
Block a user