Improve creation of cached getters

This commit is contained in:
andig 2020-04-03 18:58:44 +02:00
parent b674553747
commit 8d84892069
5 changed files with 148 additions and 76 deletions

View file

@ -2,107 +2,126 @@ package provider
import (
"time"
"github.com/benbjohnson/clock"
)
// CacheGetter wraps a getter with a cache
type CacheGetter struct {
// Cached wraps a getter with a cache
type Cached struct {
clck clock.Clock
updated time.Time
cache time.Duration
getter interface{}
val interface{}
}
// NewCacheGetter wraps a getter with a cache
func NewCacheGetter(getter interface{}, cache time.Duration) *CacheGetter {
if g, ok := getter.(func() (float64, error)); ok {
getter = FloatGetter(g)
}
if g, ok := getter.(func() (int64, error)); ok {
getter = IntGetter(g)
}
return &CacheGetter{
// NewCached wraps a getter with a cache
func NewCached(getter interface{}, cache time.Duration) *Cached {
return &Cached{
clck: clock.New(),
getter: getter,
cache: cache,
}
}
// FloatGetter gets float value
func (c *CacheGetter) FloatGetter() (float64, error) {
if time.Since(c.updated) > c.cache {
g, ok := c.getter.(FloatGetter)
if !ok {
func (c *Cached) FloatGetter() FloatGetter {
g, ok := c.getter.(FloatGetter)
if !ok {
if g, ok = c.getter.(func() (float64, error)); !ok {
log.FATAL.Fatalf("invalid type: %T", c.getter)
}
val, err := g()
if err != nil {
return val, err
}
c.updated = time.Now()
c.val = val
g = FloatGetter(g)
}
return c.val.(float64), nil
return FloatGetter(func() (float64, error) {
if c.clck.Since(c.updated) > c.cache {
val, err := g()
if err != nil {
return val, err
}
c.updated = c.clck.Now()
c.val = val
}
return c.val.(float64), nil
})
}
// IntGetter gets int value
func (c *CacheGetter) IntGetter() (int64, error) {
if time.Since(c.updated) > c.cache {
g, ok := c.getter.(IntGetter)
if !ok {
func (c *Cached) IntGetter() IntGetter {
g, ok := c.getter.(IntGetter)
if !ok {
if g, ok = c.getter.(func() (int64, error)); !ok {
log.FATAL.Fatalf("invalid type: %T", c.getter)
}
val, err := g()
if err != nil {
return val, err
}
c.updated = time.Now()
c.val = val
g = IntGetter(g)
}
return c.val.(int64), nil
return IntGetter(func() (int64, error) {
if c.clck.Since(c.updated) > c.cache {
val, err := g()
if err != nil {
return val, err
}
c.updated = c.clck.Now()
c.val = val
}
return c.val.(int64), nil
})
}
// StringGetter gets string value
func (c *CacheGetter) StringGetter() (string, error) {
if time.Since(c.updated) > c.cache {
g, ok := c.getter.(StringGetter)
if !ok {
func (c *Cached) StringGetter() StringGetter {
g, ok := c.getter.(StringGetter)
if !ok {
if g, ok = c.getter.(func() (string, error)); !ok {
log.FATAL.Fatalf("invalid type: %T", c.getter)
}
val, err := g()
if err != nil {
return val, err
}
c.updated = time.Now()
c.val = val
g = StringGetter(g)
}
return c.val.(string), nil
return StringGetter(func() (string, error) {
if c.clck.Since(c.updated) > c.cache {
val, err := g()
if err != nil {
return val, err
}
c.updated = c.clck.Now()
c.val = val
}
return c.val.(string), nil
})
}
// BoolGetter gets bool value
func (c *CacheGetter) BoolGetter() (bool, error) {
if time.Since(c.updated) > c.cache {
g, ok := c.getter.(BoolGetter)
if !ok {
func (c *Cached) BoolGetter() BoolGetter {
g, ok := c.getter.(BoolGetter)
if !ok {
if g, ok = c.getter.(func() (bool, error)); !ok {
log.FATAL.Fatalf("invalid type: %T", g)
}
val, err := g()
if err != nil {
return val, err
}
c.updated = time.Now()
c.val = val
g = BoolGetter(g)
}
return c.val.(bool), nil
return BoolGetter(func() (bool, error) {
if c.clck.Since(c.updated) > c.cache {
val, err := g()
if err != nil {
return val, err
}
c.updated = c.clck.Now()
c.val = val
}
return c.val.(bool), nil
})
}

53
provider/cache_test.go Normal file
View file

@ -0,0 +1,53 @@
package provider
import (
"errors"
"testing"
"time"
"github.com/benbjohnson/clock"
)
func TestCachedGetter(t *testing.T) {
var idx int
cases := []struct {
f float64
e error
}{
{f: 1, e: nil},
{f: 2, e: nil},
{f: 3, e: errors.New("3")},
}
g := func() (float64, error) {
f := cases[idx].f
e := cases[idx].e
idx++
return f, e
}
duration := time.Second
c := NewCached(g, duration)
clck := clock.NewMock()
c.clck = clck
getter := c.FloatGetter()
expect := func(s struct {
f float64
e error
}) {
f, e := getter()
if f != s.f || e != s.e {
t.Errorf("unexpected cache value: %f, %v\n", f, e)
}
}
expect(cases[0])
expect(cases[0])
clck.Add(2 * duration)
expect(cases[1])
clck.Add(2 * duration)
expect(cases[2])
}

View file

@ -36,7 +36,7 @@ func NewStringGetterFromConfig(pc *Config) (res StringGetter) {
}
if pc.Cache > 0 {
res = NewCacheGetter(res, pc.Cache).StringGetter
res = NewCached(res, pc.Cache).StringGetter()
}
return
@ -56,7 +56,7 @@ func NewBoolGetterFromConfig(pc *Config) (res BoolGetter) {
}
if pc.Cache > 0 {
res = NewCacheGetter(res, pc.Cache).BoolGetter
res = NewCached(res, pc.Cache).BoolGetter()
}
return
@ -84,7 +84,7 @@ func NewFloatGetterFromConfig(pc *Config) (res FloatGetter) {
}
if pc.Cache > 0 {
res = NewCacheGetter(res, pc.Cache).FloatGetter
res = NewCached(res, pc.Cache).FloatGetter()
}
return

View file

@ -28,7 +28,7 @@ type Audi struct {
token string
tokenValid time.Time
cache time.Duration
chargeStateG *provider.CacheGetter
chargeStateG provider.FloatGetter
}
type audiTokenResponse struct {
@ -79,7 +79,7 @@ func NewAudiFromConfig(log *api.Logger, other map[string]interface{}) api.Vehicl
cache: cc.Cache,
}
v.chargeStateG = provider.NewCacheGetter(v.chargeState, cc.Cache)
v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter()
return v
}
@ -202,5 +202,5 @@ func (v *Audi) chargeState() (float64, error) {
// ChargeState implements the Vehicle.ChargeState interface
func (v *Audi) ChargeState() (float64, error) {
return v.chargeStateG.FloatGetter()
return v.chargeStateG()
}

View file

@ -12,8 +12,8 @@ import (
type Tesla struct {
*embed
vehicle *tesla.Vehicle
chargeStateG *provider.CacheGetter
chargedEnergyG *provider.CacheGetter
chargeStateG provider.FloatGetter
chargedEnergyG provider.FloatGetter
}
// NewTeslaFromConfig creates a new Tesla vehicle
@ -47,8 +47,8 @@ func NewTeslaFromConfig(log *api.Logger, other map[string]interface{}) api.Vehic
vehicle: vehicles[0].Vehicle,
}
v.chargeStateG = provider.NewCacheGetter(v.chargeState, cc.Cache)
v.chargedEnergyG = provider.NewCacheGetter(v.chargedEnergy, cc.Cache)
v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter()
v.chargedEnergyG = provider.NewCached(v.chargedEnergy, cc.Cache).FloatGetter()
return v
}
@ -61,7 +61,7 @@ func (v *Tesla) chargeState() (float64, error) {
// ChargeState implements the Vehicle.ChargeState interface
func (v *Tesla) ChargeState() (float64, error) {
return v.chargeStateG.FloatGetter()
return v.chargeStateG()
}
// chargedEnergy implements the ChargeRater.ChargedEnergy interface
@ -72,7 +72,7 @@ func (v *Tesla) chargedEnergy() (float64, error) {
// ChargedEnergy implements the ChargeRater.ChargedEnergy interface
func (v *Tesla) ChargedEnergy() (float64, error) {
return v.chargedEnergyG.FloatGetter()
return v.chargedEnergyG()
}
// depends on https://github.com/jsgoecke/tesla/issues/28