Add persistent settings (#4751)
This commit is contained in:
parent
7a94bb1e6a
commit
8c9918a770
5 changed files with 181 additions and 2 deletions
10
api/store/types.go
Normal file
10
api/store/types.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package store
|
||||
|
||||
// Provider creates a Persister for given string key
|
||||
type Provider func(string) Store
|
||||
|
||||
// Store can load and store data
|
||||
type Store interface {
|
||||
Load(any) error
|
||||
Save(any) error
|
||||
}
|
||||
15
cmd/setup.go
15
cmd/setup.go
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/evcc-io/evcc/push"
|
||||
"github.com/evcc-io/evcc/server"
|
||||
"github.com/evcc-io/evcc/server/db"
|
||||
"github.com/evcc-io/evcc/server/db/settings"
|
||||
"github.com/evcc-io/evcc/tariff"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/locale"
|
||||
|
|
@ -74,7 +75,7 @@ func configureEnvironment(cmd *cobra.Command, conf config) (err error) {
|
|||
}
|
||||
|
||||
// setup sponsorship
|
||||
if conf.SponsorToken != "" {
|
||||
if err == nil && conf.SponsorToken != "" {
|
||||
err = sponsor.ConfigureSponsorship(conf.SponsorToken)
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +118,17 @@ func configureEnvironment(cmd *cobra.Command, conf config) (err error) {
|
|||
|
||||
// configureDatabase configures session database
|
||||
func configureDatabase(conf dbConfig) error {
|
||||
return db.NewInstance(conf.Type, conf.Dsn)
|
||||
err := db.NewInstance(conf.Type, conf.Dsn)
|
||||
if err == nil {
|
||||
if err = settings.Init(); err == nil {
|
||||
shutdown.Register(func() {
|
||||
if err := settings.Persist(); err != nil {
|
||||
log.ERROR.Println("cannot save settings:", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// configureInflux configures influx database
|
||||
|
|
|
|||
105
server/db/settings/setting.go
Normal file
105
server/db/settings/setting.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/evcc-io/evcc/server/db"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
// setting is a settings entry
|
||||
type setting struct {
|
||||
Key string `json:"key" gorm:"primarykey"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
var (
|
||||
settings []setting
|
||||
dirty int32
|
||||
)
|
||||
|
||||
func Init() error {
|
||||
err := db.Instance.AutoMigrate(new(setting))
|
||||
if err == nil {
|
||||
err = db.Instance.Find(&settings).Error
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Persist() error {
|
||||
dirty := atomic.CompareAndSwapInt32(&dirty, 1, 0)
|
||||
if !dirty || len(settings) == 0 {
|
||||
// avoid "empty slice found"
|
||||
return nil
|
||||
}
|
||||
return db.Instance.Save(settings).Error
|
||||
}
|
||||
|
||||
func SetString(key string, val string) {
|
||||
idx := slices.IndexFunc(settings, func(s setting) bool {
|
||||
return s.Key == key
|
||||
})
|
||||
|
||||
if idx < 0 {
|
||||
settings = append(settings, setting{key, val})
|
||||
atomic.StoreInt32(&dirty, 1)
|
||||
} else if settings[idx].Value != val {
|
||||
settings[idx].Value = val
|
||||
atomic.StoreInt32(&dirty, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func SetInt(key string, val int64) {
|
||||
SetString(key, strconv.FormatInt(val, 10))
|
||||
}
|
||||
|
||||
func SetFloat(key string, val float64) {
|
||||
SetString(key, strconv.FormatFloat(val, 'f', -1, 64))
|
||||
}
|
||||
|
||||
func SetJson(key string, val any) error {
|
||||
b, err := json.Marshal(val)
|
||||
if err == nil {
|
||||
SetString(key, string(b))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func String(key string) (string, error) {
|
||||
idx := slices.IndexFunc(settings, func(s setting) bool {
|
||||
return s.Key == key
|
||||
})
|
||||
if idx < 0 {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
return settings[idx].Value, nil
|
||||
}
|
||||
|
||||
func Int(key string) (int64, error) {
|
||||
s, err := String(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
|
||||
func Float(key string) (float64, error) {
|
||||
s, err := String(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseFloat(s, 64)
|
||||
}
|
||||
|
||||
func Json(key string, res any) error {
|
||||
s, err := String(key)
|
||||
if err == nil {
|
||||
err = json.Unmarshal([]byte(s), &res)
|
||||
}
|
||||
return err
|
||||
}
|
||||
32
server/db/settings/settings_test.go
Normal file
32
server/db/settings/settings_test.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
v := "foo"
|
||||
SetString("string", v)
|
||||
res, err := String("string")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, v, res)
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
v := int64(math.MaxInt64)
|
||||
SetInt("int64", v)
|
||||
res, err := Int("int64")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, v, res)
|
||||
}
|
||||
|
||||
func TestFloat(t *testing.T) {
|
||||
v := 3.141
|
||||
SetFloat("float64", v)
|
||||
res, err := Float("float64")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, v, res)
|
||||
}
|
||||
21
server/db/settings/store.go
Normal file
21
server/db/settings/store.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package settings
|
||||
|
||||
import "github.com/evcc-io/evcc/api/store"
|
||||
|
||||
type storer struct {
|
||||
key string
|
||||
}
|
||||
|
||||
var _ store.Store = (*storer)(nil)
|
||||
|
||||
func NewStore(key string) store.Store {
|
||||
return &storer{key: key}
|
||||
}
|
||||
|
||||
func (s *storer) Load(res any) error {
|
||||
return Json(s.key, &res)
|
||||
}
|
||||
|
||||
func (s *storer) Save(val any) error {
|
||||
return SetJson(s.key, val)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue