2024-01-10 06:12:10 +05:30
|
|
|
package mojang
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/jellydator/ttlcache/v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
type MojangApiTexturesProvider struct {
|
2024-01-30 13:35:04 +05:30
|
|
|
MojangApiTexturesEndpoint func(uuid string, signed bool) (*ProfileResponse, error)
|
2024-01-10 06:12:10 +05:30
|
|
|
}
|
|
|
|
|
2024-01-30 13:35:04 +05:30
|
|
|
func (ctx *MojangApiTexturesProvider) GetTextures(uuid string) (*ProfileResponse, error) {
|
2024-01-10 06:12:10 +05:30
|
|
|
return ctx.MojangApiTexturesEndpoint(uuid, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perfectly there should be an object with provider and cache implementation,
|
|
|
|
// but I decided not to introduce a layer and just implement cache in place.
|
|
|
|
type TexturesProviderWithInMemoryCache struct {
|
|
|
|
provider TexturesProvider
|
|
|
|
once sync.Once
|
2024-01-30 13:35:04 +05:30
|
|
|
cache *ttlcache.Cache[string, *ProfileResponse]
|
2024-01-10 06:12:10 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
func NewTexturesProviderWithInMemoryCache(provider TexturesProvider) *TexturesProviderWithInMemoryCache {
|
|
|
|
storage := &TexturesProviderWithInMemoryCache{
|
|
|
|
provider: provider,
|
2024-01-30 13:35:04 +05:30
|
|
|
cache: ttlcache.New[string, *ProfileResponse](
|
|
|
|
ttlcache.WithDisableTouchOnHit[string, *ProfileResponse](),
|
2024-01-10 06:12:10 +05:30
|
|
|
// I'm aware of ttlcache.WithLoader(), but it doesn't allow to return an error
|
|
|
|
),
|
|
|
|
}
|
|
|
|
|
|
|
|
return storage
|
|
|
|
}
|
|
|
|
|
2024-01-30 13:35:04 +05:30
|
|
|
func (s *TexturesProviderWithInMemoryCache) GetTextures(uuid string) (*ProfileResponse, error) {
|
2024-01-10 06:12:10 +05:30
|
|
|
item := s.cache.Get(uuid)
|
|
|
|
// Don't check item.IsExpired() since Get function is already did this check
|
|
|
|
if item != nil {
|
|
|
|
return item.Value(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
result, err := s.provider.GetTextures(uuid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.cache.Set(uuid, result, time.Minute)
|
|
|
|
// Call it only after first set so GC will work more often
|
|
|
|
s.startGcOnce()
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *TexturesProviderWithInMemoryCache) StopGC() {
|
|
|
|
// If you call the Stop() on a non-started GC, the process will hang trying to close the uninitialized channel
|
|
|
|
s.startGcOnce()
|
|
|
|
s.cache.Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *TexturesProviderWithInMemoryCache) startGcOnce() {
|
|
|
|
s.once.Do(func() {
|
|
|
|
go s.cache.Start()
|
|
|
|
})
|
|
|
|
}
|