vinegar/vinegarUtil/webLRU.go

223 lines
4.7 KiB
Go

package vinegarUtil
import (
"errors"
"fmt"
"strings"
"time"
)
const (
DefaultCacheTimeInMinutes = 1
)
type (
lru map[string]*LruEntry
Lru struct {
entries *lru
limit int64
currentSize int64
}
LruEntry struct {
path string
Content []byte
CompressedContent []byte
created time.Time
expires time.Time
mostRecentAccess time.Time
totalSize int64
MimeType string
}
SingleFileCache struct {
path string
entry *LruEntry
}
Cache interface {
Get(key string) (*LruEntry, bool)
GetFresh(key string) (*LruEntry, bool)
Put(key string, pathlike string) error
Remove(key string)
}
)
func NewLRU(size int64) *Lru {
cLru := make(lru)
return &Lru{&cLru, size, 0}
}
func NewSingleFileCache(pathlike string) *SingleFileCache {
entry := newLRUEntry(pathlike)
sfc := SingleFileCache{path: pathlike, entry: entry}
return &sfc
}
func (l *Lru) Get(key string) (*LruEntry, bool) {
entry, exists := (*l.entries)[key]
if exists && entry.expires.Before(time.Now()) {
fmt.Println("Cache miss due to expired content")
l.Remove(key)
return nil, false
} else {
if exists {
entry.mostRecentAccess = time.Now()
}
return entry, exists
}
}
func (l *Lru) GetFresh(key string) (*LruEntry, bool) {
l.Remove(key)
return l.Get(key)
}
func (l *Lru) Put(key string, pathlike string) error {
entry, exists := (*l.entries)[key]
var size int64
if exists {
content, fExists := GetDiskContent(pathlike)
if !fExists {
return errors.New("attempted to refresh cache with file that no longer exists on disk")
}
zippedContent := *GZipBytes(content)
size = int64(len(*content)) + int64(len(zippedContent))
l.ensureVacancy(size)
entry.Content = *content
entry.CompressedContent = zippedContent
entry.created = time.Now()
entry.expires = time.Now().Add(DefaultCacheTimeInMinutes * time.Minute)
entry.mostRecentAccess = time.Now()
entry.totalSize = size
} else {
entry = newLRUEntry(pathlike)
}
(*l.entries)[key] = entry
l.recalcSize()
return nil
}
func (l *Lru) Remove(key string) {
entry, exists := (*l.entries)[key]
if exists {
size := entry.totalSize
delete(*l.entries, key)
l.currentSize = l.currentSize - size
}
}
func (l *Lru) ensureVacancy(required int64) {
if l.currentSize == 0 {
return
}
for l.currentSize+required > l.limit {
leastKey := l.getLeastRecentlyUsedKey()
l.Remove(leastKey)
}
}
func (l *Lru) getLeastRecentlyUsedKey() string {
started := false
var benchmark *LruEntry
leastUsedKey := ""
for key, entry := range *l.entries {
if !started {
started = true
benchmark = entry
leastUsedKey = key
continue
}
if benchmark.mostRecentAccess.After(entry.mostRecentAccess) {
benchmark = entry
}
}
if leastUsedKey == "" {
panic("Invalid key for LRU: [" + leastUsedKey + "]")
}
return leastUsedKey
}
func (l *Lru) recalcSize() {
var total int64
for _, entry := range *l.entries {
total += entry.totalSize
}
l.currentSize = total
}
func (sfc *SingleFileCache) Get(key string) (*LruEntry, bool) {
return sfc.entry, true
}
func (sfc *SingleFileCache) GetFresh(key string) (*LruEntry, bool) {
entry := newLRUEntry(sfc.entry.path)
sfc.entry = entry
return entry, true
}
// Put THIS WILL DELETE ANYTHING YOU HAVE STORED IN THIS CACHE.
func (sfc *SingleFileCache) Put(key string, pathlike string) error {
//there's a bug in this. we don't return an error from newLRUEntry, so if the file disappears, the server will die
entry := newLRUEntry(pathlike)
sfc.entry = entry
return nil
}
func (sfc *SingleFileCache) Remove(key string) {
//i'm actually not sure what to do here. why would you ever remove from a single-file cache?
}
func newLRUEntry(pathlike string) *LruEntry {
bits, exists := GetDiskContent(pathlike)
if !exists {
panic("Could not load single file for single file path: " + pathlike)
}
compressedBits := GZipBytes(bits)
size := int64(len(*bits)) + int64(len(*compressedBits))
entry := LruEntry{
path: pathlike,
Content: *bits,
CompressedContent: *compressedBits,
created: time.Now(),
expires: time.Now().Add(DefaultCacheTimeInMinutes * time.Minute),
mostRecentAccess: time.Now(),
totalSize: size,
MimeType: GuessMimetype(pathlike),
}
return &entry
}
func GuessMimetype(filePath string) string {
dotIndex := strings.LastIndex(filePath, ".")
ext := filePath[dotIndex+1:]
switch ext {
case "htm":
case "html":
return "text/html"
case "ico":
return "image/vnd.microsoft.icon"
case "jpg":
case "jpeg":
return "image/jpeg"
case "png":
return "image/png"
case "svg":
return "image/svg+xml"
case "css":
return "text/css"
default:
return "application/octet-stream"
}
return "application/octet-stream"
}