package s3fs
import (
"container/list"
"sync"
"time"
"github.com/c4pt0r/agfs/agfs-server/pkg/filesystem"
)
type DirCacheEntry struct {
Files []filesystem.FileInfo
ModTime time.Time
}
type StatCacheEntry struct {
Info *filesystem.FileInfo
ModTime time.Time
}
type ListDirCache struct {
mu sync.RWMutex
cache map[string]*list.Element
lruList *list.List
maxSize int
ttl time.Duration
enabled bool
hitCount uint64
missCount uint64
}
type dirCacheItem struct {
path string
entry *DirCacheEntry
}
func NewListDirCache(maxSize int, ttl time.Duration, enabled bool) *ListDirCache {
if maxSize <= 0 {
maxSize = 1000
}
if ttl <= 0 {
ttl = 30 * time.Second
}
return &ListDirCache{
cache: make(map[string]*list.Element),
lruList: list.New(),
maxSize: maxSize,
ttl: ttl,
enabled: enabled,
}
}
func (c *ListDirCache) Get(path string) ([]filesystem.FileInfo, bool) {
if !c.enabled {
return nil, false
}
c.mu.Lock()
defer c.mu.Unlock()
elem, ok := c.cache[path]
if !ok {
c.missCount++
return nil, false
}
item := elem.Value.(*dirCacheItem)
if time.Since(item.entry.ModTime) > c.ttl {
c.lruList.Remove(elem)
delete(c.cache, path)
c.missCount++
return nil, false
}
c.lruList.MoveToFront(elem)
c.hitCount++
files := make([]filesystem.FileInfo, len(item.entry.Files))
copy(files, item.entry.Files)
return files, true
}
func (c *ListDirCache) Put(path string, files []filesystem.FileInfo) {
if !c.enabled {
return
}
c.mu.Lock()
defer c.mu.Unlock()
if elem, ok := c.cache[path]; ok {
item := elem.Value.(*dirCacheItem)
item.entry.Files = files
item.entry.ModTime = time.Now()
c.lruList.MoveToFront(elem)
return
}
entry := &DirCacheEntry{
Files: files,
ModTime: time.Now(),
}
item := &dirCacheItem{
path: path,
entry: entry,
}
elem := c.lruList.PushFront(item)
c.cache[path] = elem
if c.lruList.Len() > c.maxSize {
oldest := c.lruList.Back()
if oldest != nil {
c.lruList.Remove(oldest)
oldestItem := oldest.Value.(*dirCacheItem)
delete(c.cache, oldestItem.path)
}
}
}
func (c *ListDirCache) Invalidate(path string) {
if !c.enabled {
return
}
c.mu.Lock()
defer c.mu.Unlock()
if elem, ok := c.cache[path]; ok {
c.lruList.Remove(elem)
delete(c.cache, path)
}
}
func (c *ListDirCache) InvalidatePrefix(prefix string) {
if !c.enabled {
return
}
c.mu.Lock()
defer c.mu.Unlock()
toDelete := make([]string, 0)
for path := range c.cache {
if path == prefix || (len(path) > len(prefix) && path[:len(prefix)] == prefix && path[len(prefix)] == '/') {
toDelete = append(toDelete, path)
}
}
for _, path := range toDelete {
if elem, ok := c.cache[path]; ok {
c.lruList.Remove(elem)
delete(c.cache, path)
}
}
}
func (c *ListDirCache) Clear() {
if !c.enabled {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[string]*list.Element)
c.lruList = list.New()
}
type StatCache struct {
mu sync.RWMutex
cache map[string]*list.Element
lruList *list.List
maxSize int
ttl time.Duration
enabled bool
hitCount uint64
missCount uint64
}
type statCacheItem struct {
path string
entry *StatCacheEntry
}
func NewStatCache(maxSize int, ttl time.Duration, enabled bool) *StatCache {
if maxSize <= 0 {
maxSize = 5000
}
if ttl <= 0 {
ttl = 60 * time.Second
}
return &StatCache{
cache: make(map[string]*list.Element),
lruList: list.New(),
maxSize: maxSize,
ttl: ttl,
enabled: enabled,
}
}
func (c *StatCache) Get(path string) (*filesystem.FileInfo, bool) {
if !c.enabled {
return nil, false
}
c.mu.Lock()
defer c.mu.Unlock()
elem, ok := c.cache[path]
if !ok {
c.missCount++
return nil, false
}
item := elem.Value.(*statCacheItem)
if time.Since(item.entry.ModTime) > c.ttl {
c.lruList.Remove(elem)
delete(c.cache, path)
c.missCount++
return nil, false
}
c.lruList.MoveToFront(elem)
c.hitCount++
info := *item.entry.Info
return &info, true
}
func (c *StatCache) Put(path string, info *filesystem.FileInfo) {
if !c.enabled || info == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
if elem, ok := c.cache[path]; ok {
item := elem.Value.(*statCacheItem)
item.entry.Info = info
item.entry.ModTime = time.Now()
c.lruList.MoveToFront(elem)
return
}
entry := &StatCacheEntry{
Info: info,
ModTime: time.Now(),
}
item := &statCacheItem{
path: path,
entry: entry,
}
elem := c.lruList.PushFront(item)
c.cache[path] = elem
if c.lruList.Len() > c.maxSize {
oldest := c.lruList.Back()
if oldest != nil {
c.lruList.Remove(oldest)
oldestItem := oldest.Value.(*statCacheItem)
delete(c.cache, oldestItem.path)
}
}
}
func (c *StatCache) Invalidate(path string) {
if !c.enabled {
return
}
c.mu.Lock()
defer c.mu.Unlock()
if elem, ok := c.cache[path]; ok {
c.lruList.Remove(elem)
delete(c.cache, path)
}
}
func (c *StatCache) InvalidatePrefix(prefix string) {
if !c.enabled {
return
}
c.mu.Lock()
defer c.mu.Unlock()
toDelete := make([]string, 0)
for path := range c.cache {
if path == prefix || (len(path) > len(prefix) && path[:len(prefix)] == prefix && path[len(prefix)] == '/') {
toDelete = append(toDelete, path)
}
}
for _, path := range toDelete {
if elem, ok := c.cache[path]; ok {
c.lruList.Remove(elem)
delete(c.cache, path)
}
}
}
func (c *StatCache) Clear() {
if !c.enabled {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[string]*list.Element)
c.lruList = list.New()
}