diff --git a/.gitignore b/.gitignore index 5e1694d75..d20366944 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ evcc linux-*.Dockerfile !evcc.dist.yaml !modules/** +dist diff --git a/.goreleaser.yml b/.goreleaser.yml index 5618c3bdc..8622cb1db 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -8,13 +8,13 @@ release: before: hooks: # you may remove this if you don't need go generate - - go generate ./... + - make assets builds: - id: evcc # Path to main.go file or main package. main: main.go ldflags: - - -X github.com/andig/evcc.Version={{.Version}} -X github.com/andig/evcc.Commit={{.ShortCommit}} + - -X github.com/andig/evcc/server.Version={{.Version}} -X github.com/andig/evcc/server.Commit={{.ShortCommit}} env: - CGO_ENABLED=0 goos: @@ -25,6 +25,17 @@ builds: - arm goarm: - "6" +archives: + - builds: + - evcc + replacements: + amd64: 64-bit + 386: 32-bit + darwin: macOS + format: tar.gz + format_overrides: + - goos: windows + format: zip checksum: name_template: 'checksums.txt' snapshot: diff --git a/provider/cache.go b/provider/cache.go index e86bf8719..d96e8b2c8 100644 --- a/provider/cache.go +++ b/provider/cache.go @@ -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 + }) } diff --git a/provider/cache_test.go b/provider/cache_test.go new file mode 100644 index 000000000..8bc1a71b5 --- /dev/null +++ b/provider/cache_test.go @@ -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]) +} diff --git a/provider/config.go b/provider/config.go index b6781f044..10b3c36f0 100644 --- a/provider/config.go +++ b/provider/config.go @@ -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 diff --git a/vehicle/audi.go b/vehicle/audi.go index a5979c859..204f2efff 100644 --- a/vehicle/audi.go +++ b/vehicle/audi.go @@ -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() } diff --git a/vehicle/tesla.go b/vehicle/tesla.go index 2b4cd69f7..3172276c6 100644 --- a/vehicle/tesla.go +++ b/vehicle/tesla.go @@ -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