Deretardify the caching

This commit is contained in:
2025-10-11 10:33:03 +02:00
parent 6c1e9310c5
commit 22b218c7d3
6 changed files with 401 additions and 476 deletions

View File

@@ -2,8 +2,6 @@ package esi
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
@@ -16,14 +14,14 @@ import (
)
type CacheDB interface {
GetCacheEntry(urlHash string) (*types.CacheEntry, error)
GetCacheEntry(hash string) (*types.CacheEntry, error)
SaveCacheEntry(entry *types.CacheEntry) error
}
// CachedESI implements ESIInterface with caching
type CachedESI struct {
direct ESIInterface
db CacheDB
direct ESIInterface
db CacheDB
cacheValidity time.Duration
}
@@ -47,135 +45,127 @@ func NewCachedESI(direct ESIInterface, db CacheDB) *CachedESI {
// GetPlanetPI retrieves detailed information about a specific planet with caching
func (c *CachedESI) GetPlanetPI(ctx context.Context, characterID int, planetID int64, accessToken string) (*PlanetPI, error) {
funclog := logger.Default.WithPrefix("CachedESI.GetPlanetPI").WithPrefix(fmt.Sprintf("characterID=%d", characterID)).WithPrefix(fmt.Sprintf("planetID=%d", planetID))
url := fmt.Sprintf("/v3/characters/%d/planets/%d/", characterID, planetID)
funclog.Info("Starting")
fetchFunc := func() (*PlanetPI, error) {
funclog.Info("Calling direct ESI")
result, err := c.direct.GetPlanetPI(ctx, characterID, planetID, accessToken)
if err != nil {
funclog.Error("Direct ESI call failed: %v", err)
hash := fmt.Sprintf("GetPlanetPI-%d-%d", characterID, planetID)
cached, err := c.db.GetCacheEntry(hash)
if err != nil {
funclog.Warning("Failed to get cache entry: %v", err)
}
if cached != nil {
funclog.Info("Cache hit for GetPlanetPI-%d-%d", characterID, planetID)
var planetPI PlanetPI
if err := json.Unmarshal([]byte(cached.Data), &planetPI); err != nil {
funclog.Error("Failed to unmarshal cached data: %v", err)
return nil, err
}
if result != nil {
funclog.Info("Direct ESI returned planet details with %d pins", len(result.Pins))
} else {
funclog.Info("Direct ESI returned nil planet details")
}
return result, nil
return &planetPI, nil
}
result, err := getCachedResponse(c, url, fetchFunc)
funclog.Info("Cache miss for GetPlanetPI-%d-%d", characterID, planetID)
apiresp, err := c.direct.GetPlanetPI(ctx, characterID, planetID, accessToken)
if err != nil {
funclog.Error("Failed to get planet details: %v", err)
funclog.Error("Failed to get planet PI: %v", err)
return nil, err
}
if result != nil {
funclog.Info("Successfully retrieved planet details with %d pins", len(result.Pins))
} else {
funclog.Info("Successfully retrieved nil planet details")
}
return result, nil
}
// getCachedResponse handles caching logic with generics
func getCachedResponse[T any](c *CachedESI, url string, fetchFunc func() (T, error)) (T, error) {
funclog := logger.Default.WithPrefix("getCachedResponse").WithPrefix(fmt.Sprintf("url=%s", url))
// Generate cache key
hash := sha256.Sum256([]byte(url))
urlHash := hex.EncodeToString(hash[:])
funclog.Debug("Generated cache key %s for URL: %s", urlHash[:8], url)
// Check cache using the interface
funclog.Debug("Checking cache for URL: %s", url)
cacheEntry, err := c.db.GetCacheEntry(urlHash)
if err == nil {
// Check if cache is still valid
age := time.Since(cacheEntry.CachedAt)
if age < c.cacheValidity {
funclog.Info("Cache HIT for URL: %s (cached %v ago, valid for %v more)", url, age, c.cacheValidity-age)
// Parse cached response
var result T
if err := json.Unmarshal([]byte(cacheEntry.Response), &result); err == nil {
funclog.Debug("Successfully unmarshaled cached response for URL: %s", url)
return result, nil
}
funclog.Warning("Failed to unmarshal cached response for URL: %s: %v", url, err)
} else {
logger.Info("Cache EXPIRED for URL: %s (cached %v ago, validity: %v)", url, age, c.cacheValidity)
}
} else {
funclog.Info("Cache MISS for URL: %s (no cache entry found): %v", url, err)
}
// Cache miss or invalid, fetch from API
funclog.Info("Fetching from API for URL: %s", url)
result, err := fetchFunc()
apirespjson, err := json.Marshal(apiresp)
if err != nil {
funclog.Error("API fetch failed for URL: %s: %v", url, err)
var zero T
return zero, err
funclog.Error("Failed to marshal planet PI: %v", err)
return nil, err
}
funclog.Debug("API fetch successful for URL: %s", url)
// Store in cache
funclog.Debug("Marshaling response for caching URL: %s", url)
responseBytes, err := json.Marshal(result)
if err != nil {
funclog.Warning("Failed to marshal response for caching URL: %s: %v", url, err)
return result, nil
}
funclog.Debug("Response marshaled successfully for URL: %s (%d bytes)", url, len(responseBytes))
cacheEntry = &types.CacheEntry{
URLHash: urlHash,
Response: string(responseBytes),
cacheEntry := &types.CacheEntry{
Hash: hash,
Data: string(apirespjson),
CachedAt: time.Now(),
}
funclog.Debug("Saving cache entry for URL: %s", url)
if err := c.db.SaveCacheEntry(cacheEntry); err != nil {
funclog.Warning("Failed to cache response for URL: %s: %v", url, err)
} else {
funclog.Info("Cached response for URL: %s (valid for %v, %d bytes)", url, c.cacheValidity, len(responseBytes))
err = c.db.SaveCacheEntry(cacheEntry)
if err != nil {
funclog.Warning("Failed to save cache entry: %v", err)
}
return result, nil
return apiresp, nil
}
// GetPlanetName retrieves planet name with caching
func (c *CachedESI) GetPlanetName(ctx context.Context, planetID int64) (*PlanetName, error) {
funclog := logger.Default.WithPrefix("CachedESI.GetPlanetName").WithPrefix(fmt.Sprintf("planetID=%d", planetID))
url := fmt.Sprintf("/v1/universe/planets/%d/", planetID)
funclog.Info("Starting")
fetchFunc := func() (*PlanetName, error) {
funclog.Info("Calling direct ESI for planet %d name", planetID)
result, err := c.direct.GetPlanetName(ctx, planetID)
if err != nil {
funclog.Error("Direct ESI call failed for planet %d: %v", planetID, err)
hash := fmt.Sprintf("GetPlanetName-%d", planetID)
cached, err := c.db.GetCacheEntry(hash)
if err != nil {
funclog.Warning("Failed to get cache entry: %v", err)
}
if cached != nil {
funclog.Info("Cache hit for GetPlanetName-%d", planetID)
var planetPI PlanetName
if err := json.Unmarshal([]byte(cached.Data), &planetPI); err != nil {
funclog.Error("Failed to unmarshal cached data: %v", err)
return nil, err
}
if result != nil {
funclog.Info("Direct ESI returned planet name '%s' for planet %d", result.Name, planetID)
} else {
funclog.Info("Direct ESI returned nil planet name for planet %d", planetID)
}
return result, nil
return &planetPI, nil
}
result, err := getCachedResponse(c, url, fetchFunc)
funclog.Info("Cache miss for GetPlanetName-%d", planetID)
apiresp, err := c.direct.GetPlanetName(ctx, planetID)
if err != nil {
funclog.Error("Failed to get planet name for planet %d: %v", planetID, err)
funclog.Error("Failed to get planet name: %v", err)
return nil, err
}
if result != nil {
funclog.Info("Successfully retrieved planet name '%s' for planet %d", result.Name, planetID)
} else {
funclog.Info("Successfully retrieved nil planet name for planet %d", planetID)
apirespjson, err := json.Marshal(apiresp)
if err != nil {
funclog.Error("Failed to marshal planet name: %v", err)
return nil, err
}
return result, nil
cacheEntry := &types.CacheEntry{
Hash: hash,
Data: string(apirespjson),
CachedAt: time.Now(),
}
err = c.db.SaveCacheEntry(cacheEntry)
if err != nil {
funclog.Warning("Failed to save cache entry: %v", err)
}
return apiresp, nil
}
func (c *CachedESI) GetCharacterPlanets(ctx context.Context, characterID int, accessToken string) ([]Planet, error) {
funclog := logger.Default.WithPrefix("CachedESI.GetCharacterPlanets").WithPrefix(fmt.Sprintf("characterID=%d", characterID))
funclog.Info("Starting")
hash := fmt.Sprintf("GetCharacterPlanets-%d", characterID)
cached, err := c.db.GetCacheEntry(hash)
if err != nil {
funclog.Warning("Failed to get cache entry: %v", err)
}
if cached != nil {
funclog.Info("Cache hit for GetCharacterPlanets-%d", characterID)
var planets []Planet
if err := json.Unmarshal([]byte(cached.Data), &planets); err != nil {
funclog.Error("Failed to unmarshal cached data: %v", err)
return nil, err
}
return planets, nil
}
funclog.Info("Cache miss for GetCharacterPlanets-%d", characterID)
apiresp, err := c.direct.GetCharacterPlanets(ctx, characterID, accessToken)
if err != nil {
funclog.Error("Failed to get character planets: %v", err)
return nil, err
}
apirespjson, err := json.Marshal(apiresp)
if err != nil {
funclog.Error("Failed to marshal character planets: %v", err)
return nil, err
}
cacheEntry := &types.CacheEntry{
Hash: hash,
Data: string(apirespjson),
CachedAt: time.Now(),
}
err = c.db.SaveCacheEntry(cacheEntry)
if err != nil {
funclog.Warning("Failed to save cache entry: %v", err)
}
return apiresp, nil
}
var _ ESIInterface = &CachedESI{}