package sqlfs
import (
"container/list"
"sync"
"time"
"github.com/c4pt0r/agfs/agfs-server/pkg/filesystem"
)
type CacheEntry struct {
Files []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 cacheItem struct {
path string
entry *CacheEntry
}
func NewListDirCache(maxSize int, ttl time.Duration, enabled bool) *ListDirCache {
if maxSize <= 0 {
maxSize = 1000
}
if ttl <= 0 {
ttl = 5 * 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.(*cacheItem)
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.(*cacheItem)
item.entry.Files = files
item.entry.ModTime = time.Now()
c.lruList.MoveToFront(elem)
return
}
entry := &CacheEntry{
Files: files,
ModTime: time.Now(),
}
item := &cacheItem{
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.(*cacheItem)
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 || isDescendant(path, 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) InvalidateParent(path string) {
parent := getParentPath(path)
c.Invalidate(parent)
}
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()
}
func isDescendant(path, parent string) bool {
if path == parent {
return false
}
if parent == "/" {
return path != "/"
}
if len(path) <= len(parent) {
return false
}
return path[:len(parent)] == parent && path[len(parent)] == '/'
}