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: 32 << 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 append([]byte(nil), 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 = append([]byte(nil), 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: append([]byte(nil), data...)} c.bytes += int64(len(data)) c.order.MoveToFront(e) } else { e := c.order.PushFront(thumbnailCacheEntry{key: key, data: append([]byte(nil), 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) } }