Skip to content

Commit

Permalink
Limit pcache to []byte values
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikKalkoken committed Jan 16, 2025
1 parent 04e7115 commit a9b50fc
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 35 deletions.
40 changes: 26 additions & 14 deletions internal/app/pcache/pcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,35 @@ import (
"github.com/ErikKalkoken/evebuddy/internal/app/storage"
)

// PCache is a persistent cache. It can automatically remove expired items.
type PCache struct {
st *storage.Storage
closeC chan struct{}
}

// New returns a new PCache.
//
// cleanUpTimeout is the timeout between automatic clean-up intervals. When set to 0 no cleanUp will be done.
// Make sure to close this object again to free all it's resources.
func New(st *storage.Storage, cleanUpTimeout time.Duration) *PCache {
c := &PCache{
st: st,
closeC: make(chan struct{}),
}
ticker := time.NewTicker(cleanUpTimeout)
go func() {
for {
select {
case <-c.closeC:
slog.Info("cache closed")
return
case <-ticker.C:
c.CleanUp()
if cleanUpTimeout > 0 {
ticker := time.NewTicker(cleanUpTimeout)
go func() {
for {
select {
case <-c.closeC:
slog.Info("cache closed")
return
case <-ticker.C:
c.CleanUp()
}
}
}
}()
}()
}
return c
}

Expand All @@ -49,6 +56,11 @@ func (c *PCache) Clear() {
}
}

// Close closes the cache and frees allocated resources.
func (c *PCache) Close() {
close(c.closeC)
}

func (c *PCache) Delete(key string) {
err := c.st.CacheDelete(context.Background(), key)
if err != nil {
Expand All @@ -64,7 +76,7 @@ func (c *PCache) Exists(key string) bool {
return found
}

func (c *PCache) Get(key string) (any, bool) {
func (c *PCache) Get(key string) ([]byte, bool) {
v, err := c.st.CacheGet(context.Background(), key)
if errors.Is(err, storage.ErrNotFound) {
return nil, false
Expand All @@ -76,14 +88,14 @@ func (c *PCache) Get(key string) (any, bool) {
return v, true
}

func (c *PCache) Set(key string, value any, timeout time.Duration) {
func (c *PCache) Set(key string, value []byte, timeout time.Duration) {
var expiresAt time.Time
if timeout > 0 {
expiresAt = time.Now().Add(timeout)
}
arg := storage.CacheSetParams{
Key: key,
Value: value.([]byte),
Value: value,
ExpiresAt: expiresAt,
}
err := c.st.CacheSet(context.Background(), arg)
Expand Down
30 changes: 30 additions & 0 deletions internal/app/pcache/pcache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package pcache_test

import (
"testing"
"time"

"github.com/ErikKalkoken/evebuddy/internal/app/pcache"
"github.com/ErikKalkoken/evebuddy/internal/app/storage/testutil"
"github.com/stretchr/testify/assert"
)

func TestPCache(t *testing.T) {
db, st, _ := testutil.New()
defer db.Close()
t.Run("can set and get a cache entry", func(t *testing.T) {
// given
testutil.TruncateTables(db)
c := pcache.New(st, 0)
defer c.Close()
key := "key"
value := []byte("value")
// when
c.Set(key, value, time.Minute)
// then
x, found := c.Get(key)
if assert.True(t, found) {
assert.Equal(t, value, x)
}
})
}
13 changes: 4 additions & 9 deletions internal/eveimage/eveimage.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ var (
// Defines a cache service
type CacheService interface {
Clear()
Get(string) (any, bool)
Set(string, any, time.Duration)
Get(string) ([]byte, bool)
Set(string, []byte, time.Duration)
}

// EveImageService provides cached access to images on the Eve Online image server.
Expand Down Expand Up @@ -170,7 +170,8 @@ func (m *EveImageService) InventoryTypeSKIN(id int32, size int) (fyne.Resource,
func (m *EveImageService) image(url string, timeout time.Duration) (fyne.Resource, error) {
key := "eveimage-" + makeMD5Hash(url)
var dat []byte
x, found := m.cache.Get(key)
var found bool
dat, found = m.cache.Get(key)
if !found {
if m.isOffline {
return resourceBrokenimageSvg, nil
Expand All @@ -187,12 +188,6 @@ func (m *EveImageService) image(url string, timeout time.Duration) (fyne.Resourc
return nil, err
}
dat = x.([]byte)
} else {
var ok bool
dat, ok = x.([]byte)
if !ok {
return resourceBrokenimageSvg, nil
}
}
r := fyne.NewStaticResource(key, dat)
return r, nil
Expand Down
25 changes: 23 additions & 2 deletions internal/eveimage/eveimage_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"net/http"
"os"
"testing"
"time"

"github.com/ErikKalkoken/evebuddy/internal/cache"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -54,8 +54,29 @@ func TestLoadResourceFromURL(t *testing.T) {
})
}

type cache map[string][]byte

func newCache() cache {
return make(cache)
}

func (c cache) Get(k string) ([]byte, bool) {
v, ok := c[k]
return v, ok
}

func (c cache) Set(k string, v []byte, d time.Duration) {
c[k] = v
}

func (c cache) Clear() {
for k := range c {
delete(c, k)
}
}

func TestImageFetching(t *testing.T) {
c := cache.New()
c := newCache()
httpmock.Activate()
defer httpmock.DeactivateAndReset()
dat, err := os.ReadFile("testdata/character_93330670_64.jpeg")
Expand Down
42 changes: 32 additions & 10 deletions internal/eveimage/eveimage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,35 @@ import (
"net/http"
"os"
"testing"
"time"

"github.com/ErikKalkoken/evebuddy/internal/cache"
"github.com/ErikKalkoken/evebuddy/internal/eveimage"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
)

type cache map[string][]byte

func newCache() cache {
return make(cache)
}

func (c cache) Get(k string) ([]byte, bool) {
v, ok := c[k]
return v, ok
}

func (c cache) Set(k string, v []byte, d time.Duration) {
c[k] = v
}

func (c cache) Clear() {
for k := range c {
delete(c, k)
}
}

func TestImageFetching(t *testing.T) {
c := cache.New()
httpmock.Activate()
defer httpmock.DeactivateAndReset()
dat, err := os.ReadFile("testdata/character_93330670_64.jpeg")
Expand All @@ -21,7 +41,7 @@ func TestImageFetching(t *testing.T) {
}
t.Run("can fetch an alliance logo from the image server", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -37,6 +57,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can fetch a character portrait from the image server", func(t *testing.T) {
// given
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -52,7 +73,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can fetch a corporation logo from the image server", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -68,6 +89,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can fetch a faction logo from the image server", func(t *testing.T) {
// given
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -83,7 +105,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can fetch a type icon from the image server", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -99,7 +121,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can fetch a type render from the image server", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -115,7 +137,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can fetch a type BPO from the image server", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -131,7 +153,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can fetch a type BPC from the image server", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -147,7 +169,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("should convert images size errors", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand All @@ -161,7 +183,7 @@ func TestImageFetching(t *testing.T) {
})
t.Run("can clear cache", func(t *testing.T) {
// given
c.Clear()
c := newCache()
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
Expand Down

0 comments on commit a9b50fc

Please sign in to comment.