104 lines
2.1 KiB
Go
104 lines
2.1 KiB
Go
package api
|
|
|
|
import (
|
|
"container/list"
|
|
"sync"
|
|
)
|
|
|
|
type thumbnailCacheKey struct {
|
|
Path string
|
|
Size int64
|
|
ModTime int64
|
|
}
|
|
|
|
type thumbnailCacheEntry struct {
|
|
key thumbnailCacheKey
|
|
data []byte
|
|
}
|
|
|
|
type thumbnailFlight struct {
|
|
done chan struct{}
|
|
data []byte
|
|
err error
|
|
}
|
|
|
|
type thumbnailCache struct {
|
|
mu sync.Mutex
|
|
items map[thumbnailCacheKey]*list.Element
|
|
order *list.List
|
|
flights map[thumbnailCacheKey]*thumbnailFlight
|
|
bytes int64
|
|
maxBytes int64
|
|
maxItems int
|
|
}
|
|
|
|
func newThumbnailCache() *thumbnailCache {
|
|
return &thumbnailCache{
|
|
items: make(map[thumbnailCacheKey]*list.Element),
|
|
order: list.New(),
|
|
flights: make(map[thumbnailCacheKey]*thumbnailFlight),
|
|
maxBytes: 8 << 20,
|
|
maxItems: 64,
|
|
}
|
|
}
|
|
|
|
func (c *thumbnailCache) Do(key thumbnailCacheKey, fn func() ([]byte, error)) ([]byte, error) {
|
|
c.mu.Lock()
|
|
if e, ok := c.items[key]; ok {
|
|
c.order.MoveToFront(e)
|
|
data := append([]byte(nil), e.Value.(thumbnailCacheEntry).data...)
|
|
c.mu.Unlock()
|
|
return data, nil
|
|
}
|
|
if f, ok := c.flights[key]; ok {
|
|
c.mu.Unlock()
|
|
<-f.done
|
|
return f.data, f.err
|
|
}
|
|
f := &thumbnailFlight{done: make(chan struct{})}
|
|
c.flights[key] = f
|
|
c.mu.Unlock()
|
|
|
|
data, err := fn()
|
|
if err == nil && len(data) > 0 && int64(len(data)) <= c.maxBytes {
|
|
c.mu.Lock()
|
|
c.putLocked(key, data)
|
|
f.data = data
|
|
f.err = err
|
|
delete(c.flights, key)
|
|
close(f.done)
|
|
c.mu.Unlock()
|
|
return data, nil
|
|
}
|
|
c.mu.Lock()
|
|
f.err = err
|
|
delete(c.flights, key)
|
|
close(f.done)
|
|
c.mu.Unlock()
|
|
return data, err
|
|
}
|
|
|
|
func (c *thumbnailCache) putLocked(key thumbnailCacheKey, data []byte) {
|
|
if e, ok := c.items[key]; ok {
|
|
old := e.Value.(thumbnailCacheEntry)
|
|
c.bytes -= int64(len(old.data))
|
|
e.Value = thumbnailCacheEntry{key: key, data: data}
|
|
c.bytes += int64(len(data))
|
|
c.order.MoveToFront(e)
|
|
} else {
|
|
e := c.order.PushFront(thumbnailCacheEntry{key: key, data: data})
|
|
c.items[key] = e
|
|
c.bytes += int64(len(data))
|
|
}
|
|
for c.bytes > c.maxBytes || c.order.Len() > c.maxItems {
|
|
e := c.order.Back()
|
|
if e == nil {
|
|
break
|
|
}
|
|
entry := e.Value.(thumbnailCacheEntry)
|
|
delete(c.items, entry.key)
|
|
c.bytes -= int64(len(entry.data))
|
|
c.order.Remove(e)
|
|
}
|
|
}
|