GoDoxy/internal/api/v1/favicon/cache.go
2025-04-24 15:02:31 +08:00

101 lines
2.1 KiB
Go

package favicon
import (
"encoding/json"
"time"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/jsonstore"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/route/routes"
"github.com/yusing/go-proxy/internal/task"
)
type cacheEntry struct {
Icon []byte `json:"icon"`
LastAccess time.Time `json:"lastAccess"`
}
// cache key can be absolute url or route name.
var iconCache = jsonstore.Store[*cacheEntry](common.NamespaceIconCache)
const (
iconCacheTTL = 3 * 24 * time.Hour
cleanUpInterval = time.Hour
)
func init() {
go func() {
cleanupTicker := time.NewTicker(cleanUpInterval)
defer cleanupTicker.Stop()
for {
select {
case <-task.RootContextCanceled():
return
case <-cleanupTicker.C:
pruneExpiredIconCache()
}
}
}()
}
func pruneExpiredIconCache() {
nPruned := 0
for key, icon := range iconCache.Range {
if icon.IsExpired() {
iconCache.Delete(key)
nPruned++
}
}
if nPruned > 0 {
logging.Info().Int("pruned", nPruned).Msg("pruned expired icon cache")
}
}
func routeKey(r routes.HTTPRoute) string {
return r.ProviderName() + ":" + r.Name()
}
func PruneRouteIconCache(route routes.HTTPRoute) {
iconCache.Delete(routeKey(route))
}
func loadIconCache(key string) *fetchResult {
icon, ok := iconCache.Load(key)
if ok && icon != nil {
logging.Debug().
Str("key", key).
Msg("icon found in cache")
icon.LastAccess = time.Now()
return &fetchResult{icon: icon.Icon}
}
return nil
}
func storeIconCache(key string, icon []byte) {
iconCache.Store(key, &cacheEntry{Icon: icon, LastAccess: time.Now()})
}
func (e *cacheEntry) IsExpired() bool {
return time.Since(e.LastAccess) > iconCacheTTL
}
func (e *cacheEntry) UnmarshalJSON(data []byte) error {
attempt := struct {
Icon []byte `json:"icon"`
LastAccess time.Time `json:"lastAccess"`
}{}
err := json.Unmarshal(data, &attempt)
if err == nil {
e.Icon = attempt.Icon
e.LastAccess = attempt.LastAccess
return nil
}
// fallback to bytes
err = json.Unmarshal(data, &e.Icon)
if err == nil {
e.LastAccess = time.Now()
return nil
}
return err
}