LamerAlert/config/config.go

287 lines
5.8 KiB
Go

package config
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/boltdb/bolt"
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
"code.chrissexton.org/cws/lameralert/mapping"
)
var (
bucket = []byte("config")
)
type configValue struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}
func (value configValue) getBytes() ([]byte, error) {
return json.Marshal(value)
}
func decodeConfig(in []byte, target *configValue) error {
err := json.Unmarshal(in, target)
return err
}
func encodeMapping(in mapping.Mapping) ([]byte, error) {
var out []byte
buf := bytes.NewBuffer(out)
enc := gob.NewEncoder(buf)
err := enc.Encode(in)
return buf.Bytes(), err
}
func decodeMapping(in []byte) (mapping.Mapping, error) {
var target mapping.Mapping
dec := gob.NewDecoder(bytes.NewBuffer(in))
err := dec.Decode(&target)
log.Debug().
Interface("err", err).
Str("in", string(in)).
Interface("target", target).
Msg("decodeMapping")
return target, err
}
type Config struct {
// I'm not completely sure if this mutex is necessary.
// It is left here because I'm opening an closing the db
// for every operation.
sync.Mutex
path string
}
func (c *Config) db() *bolt.DB {
db, err := bolt.Open(c.path, 0666,
&bolt.Options{
Timeout: 1 * time.Second,
})
if err != nil {
log.Fatal().Msgf("Could not open database: %s", err)
}
err = db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(bucket)
return err
})
if err != nil {
log.Fatal().Err(err).Msg("Could not open bucket")
}
return db
}
func (c *Config) getAll() (map[string]configValue, error) {
c.Lock()
defer c.Unlock()
db := c.db()
defer db.Close()
vals := map[string]configValue{}
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucket)
err := b.ForEach(func(k, v []byte) error {
var value configValue
err := decodeConfig(v, &value)
if err != nil {
return fmt.Errorf("could not convert value: %w", err)
}
vals[string(k)] = value
return nil
})
if err != nil {
return fmt.Errorf("error in forEach: %w", err)
}
return nil
})
return vals, err
}
func (c *Config) getMapping(key string) (mapping.Mapping, error) {
log.Debug().Msgf("getMapping(%s)", key)
c.Lock()
defer c.Unlock()
db := c.db()
defer db.Close()
var err error
var out mapping.Mapping
err = db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucket)
out, err = decodeMapping(b.Get([]byte(key)))
log.Debug().AnErr("err", err).Interface("out", out).Msgf("decode %s", key)
if err != nil {
log.Error().Msgf("Error decoding: %w", err)
return err
}
return nil
})
if err != nil {
log.Error().Msgf("Error getting: %w", err)
return mapping.Mapping{}, err
}
return out, nil
}
func (c *Config) get(key string) (configValue, error) {
c.Lock()
defer c.Unlock()
db := c.db()
defer db.Close()
var out configValue
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucket)
err := decodeConfig(b.Get([]byte(key)), &out)
if err != nil {
return err
}
return nil
})
if err != nil {
return configValue{}, err
}
return out, nil
}
func (c *Config) setMapping(key string, value mapping.Mapping) error {
c.Lock()
defer c.Unlock()
db := c.db()
defer db.Close()
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(bucket)
v, err := encodeMapping(value)
if err != nil {
return err
}
err = b.Put([]byte(key), v)
return err
})
if err != nil {
log.Error().Msgf("Error setting key: %s", err)
}
return err
}
func (c *Config) set(key string, value configValue) error {
c.Lock()
defer c.Unlock()
db := c.db()
defer db.Close()
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(bucket)
v, err := value.getBytes()
if err != nil {
return err
}
err = b.Put([]byte(key), v)
return err
})
if err != nil {
log.Error().Msgf("Error setting key: %s", err)
}
return err
}
func (c *Config) delete(key string) error {
c.Lock()
defer c.Unlock()
db := c.db()
defer db.Close()
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(bucket)
return b.Delete([]byte(key))
})
return err
}
// Public interface
func New(r *mux.Router) *Config {
c := &Config{
path: "lameralert.db",
}
c.setup(r)
return c
}
func (c *Config) GetInt64(key string, defaultValue int64) int64 {
v, err := c.get(key)
if err != nil {
return defaultValue
}
return int64(v.Value.(float64))
}
func (c *Config) GetInt(key string, defaultValue int) int {
v, err := c.get(key)
if err != nil {
return defaultValue
}
return int(v.Value.(float64))
}
func (c *Config) GetStringSlice(key string, defaultValue []string) []string {
v, err := c.get(key)
if err != nil {
log.Error().Msgf("GetStringSlice errored looking for %s: %s", key, err)
return defaultValue
}
return interfaceSliceToStringSlice(v.Value.([]interface{}))
}
func interfaceSliceToStringSlice(in []interface{}) []string {
out := []string{}
for _, it := range in {
out = append(out, it.(string))
}
return out
}
func (c *Config) GetStringMap(key string, defaultValue map[string]string) map[string]string {
v, err := c.get(key)
if err != nil {
return defaultValue
}
return v.Value.(map[string]string)
}
func (c *Config) GetString(key, defaultValue string) string {
v, err := c.get(key)
if err != nil {
return defaultValue
}
return v.Value.(string)
}
func (c *Config) GetMapping(key string, defaultValue mapping.Mapping) mapping.Mapping {
m, err := c.getMapping(key)
if err != nil {
log.Error().Msgf("Error: %w", err)
return defaultValue
}
return m
}
func (c *Config) GetGeneric(key string, defaultValue interface{}) interface{} {
v, err := c.get(key)
if err != nil {
return defaultValue
}
return v.Value
}
func (c *Config) SetMapping(key string, value mapping.Mapping) error {
return c.setMapping(key, value)
}
func (c *Config) Set(key string, value interface{}) error {
return c.set(key, configValue{key, value})
}