summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/wiggin77/cfg/config.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/wiggin77/cfg/config.go')
-rw-r--r--vendor/github.com/wiggin77/cfg/config.go366
1 files changed, 366 insertions, 0 deletions
diff --git a/vendor/github.com/wiggin77/cfg/config.go b/vendor/github.com/wiggin77/cfg/config.go
new file mode 100644
index 00000000..0e958102
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/config.go
@@ -0,0 +1,366 @@
+package cfg
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/wiggin77/cfg/timeconv"
+)
+
+// ErrNotFound returned when an operation is attempted on a
+// resource that doesn't exist, such as fetching a non-existing
+// property name.
+var ErrNotFound = errors.New("not found")
+
+type sourceEntry struct {
+ src Source
+ props map[string]string
+}
+
+// Config provides methods for retrieving property values from one or more
+// configuration sources.
+type Config struct {
+ mutexSrc sync.RWMutex
+ mutexListeners sync.RWMutex
+ srcs []*sourceEntry
+ chgListeners []ChangedListener
+ shutdown chan interface{}
+ wantPanicOnError bool
+}
+
+// PrependSource inserts one or more `Sources` at the beginning of
+// the list of sources such that the first source will be the
+// source checked first when resolving a property value.
+func (config *Config) PrependSource(srcs ...Source) {
+ arr := config.wrapSources(srcs...)
+
+ config.mutexSrc.Lock()
+ if config.shutdown == nil {
+ config.shutdown = make(chan interface{})
+ }
+ config.srcs = append(arr, config.srcs...)
+ config.mutexSrc.Unlock()
+
+ for _, se := range arr {
+ if _, ok := se.src.(SourceMonitored); ok {
+ config.monitor(se)
+ }
+ }
+}
+
+// AppendSource appends one or more `Sources` at the end of
+// the list of sources such that the last source will be the
+// source checked last when resolving a property value.
+func (config *Config) AppendSource(srcs ...Source) {
+ arr := config.wrapSources(srcs...)
+
+ config.mutexSrc.Lock()
+ if config.shutdown == nil {
+ config.shutdown = make(chan interface{})
+ }
+ config.srcs = append(config.srcs, arr...)
+ config.mutexSrc.Unlock()
+
+ for _, se := range arr {
+ if _, ok := se.src.(SourceMonitored); ok {
+ config.monitor(se)
+ }
+ }
+}
+
+// wrapSources wraps one or more Source's and returns
+// them as an array of `sourceEntry`.
+func (config *Config) wrapSources(srcs ...Source) []*sourceEntry {
+ arr := make([]*sourceEntry, 0, len(srcs))
+ for _, src := range srcs {
+ se := &sourceEntry{src: src}
+ config.reloadProps(se)
+ arr = append(arr, se)
+ }
+ return arr
+}
+
+// SetWantPanicOnError sets the flag determining if Config
+// should panic when `GetProps` or `GetLastModified` errors
+// for a `Source`.
+func (config *Config) SetWantPanicOnError(b bool) {
+ config.mutexSrc.Lock()
+ config.wantPanicOnError = b
+ config.mutexSrc.Unlock()
+}
+
+// ShouldPanicOnError gets the flag determining if Config
+// should panic when `GetProps` or `GetLastModified` errors
+// for a `Source`.
+func (config *Config) ShouldPanicOnError() (b bool) {
+ config.mutexSrc.RLock()
+ b = config.wantPanicOnError
+ config.mutexSrc.RUnlock()
+ return b
+}
+
+// getProp returns the value of a named property.
+// Each `Source` is checked, in the order created by adding via
+// `AppendSource` and `PrependSource`, until a value for the
+// property is found.
+func (config *Config) getProp(name string) (val string, ok bool) {
+ config.mutexSrc.RLock()
+ defer config.mutexSrc.RUnlock()
+
+ var s string
+ for _, se := range config.srcs {
+ if se.props != nil {
+ if s, ok = se.props[name]; ok {
+ val = strings.TrimSpace(s)
+ return
+ }
+ }
+ }
+ return
+}
+
+// String returns the value of the named prop as a string.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+func (config *Config) String(name string, def string) (val string, err error) {
+ if v, ok := config.getProp(name); ok {
+ val = v
+ err = nil
+ return
+ }
+
+ err = ErrNotFound
+ val = def
+ return
+}
+
+// Int returns the value of the named prop as an `int`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Int(name string, def int) (val int, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ var i int64
+ if i, err = strconv.ParseInt(s, 10, 32); err == nil {
+ val = int(i)
+ }
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Int64 returns the value of the named prop as an `int64`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Int64(name string, def int64) (val int64, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ val, err = strconv.ParseInt(s, 10, 64)
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Float64 returns the value of the named prop as a `float64`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Float64(name string, def float64) (val float64, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ val, err = strconv.ParseFloat(s, 64)
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Bool returns the value of the named prop as a `bool`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// Supports (t, true, 1, y, yes) for true, and (f, false, 0, n, no) for false,
+// all case-insensitive.
+//
+// See config.String
+func (config *Config) Bool(name string, def bool) (val bool, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ switch strings.ToLower(s) {
+ case "t", "true", "1", "y", "yes":
+ val = true
+ case "f", "false", "0", "n", "no":
+ val = false
+ default:
+ err = errors.New("invalid syntax")
+ }
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Duration returns the value of the named prop as a `time.Duration`, representing
+// a span of time.
+//
+// Units of measure are supported: ms, sec, min, hour, day, week, year.
+// See config.UnitsToMillis for a complete list of units supported.
+//
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Duration(name string, def time.Duration) (val time.Duration, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ var ms int64
+ ms, err = timeconv.ParseMilliseconds(s)
+ val = time.Duration(ms) * time.Millisecond
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// AddChangedListener adds a listener that will receive notifications
+// whenever one or more property values change within the config.
+func (config *Config) AddChangedListener(l ChangedListener) {
+ config.mutexListeners.Lock()
+ defer config.mutexListeners.Unlock()
+
+ config.chgListeners = append(config.chgListeners, l)
+}
+
+// RemoveChangedListener removes all instances of a ChangedListener.
+// Returns `ErrNotFound` if the listener was not present.
+func (config *Config) RemoveChangedListener(l ChangedListener) error {
+ config.mutexListeners.Lock()
+ defer config.mutexListeners.Unlock()
+
+ dest := make([]ChangedListener, 0, len(config.chgListeners))
+ err := ErrNotFound
+
+ // Remove all instances of the listener by
+ // copying list while filtering.
+ for _, s := range config.chgListeners {
+ if s != l {
+ dest = append(dest, s)
+ } else {
+ err = nil
+ }
+ }
+ config.chgListeners = dest
+ return err
+}
+
+// Shutdown can be called to stop monitoring of all config sources.
+func (config *Config) Shutdown() {
+ config.mutexSrc.RLock()
+ defer config.mutexSrc.RUnlock()
+ if config.shutdown != nil {
+ close(config.shutdown)
+ }
+}
+
+// onSourceChanged is called whenever one or more properties of a
+// config source has changed.
+func (config *Config) onSourceChanged(src SourceMonitored) {
+ defer func() {
+ if p := recover(); p != nil {
+ fmt.Println(p)
+ }
+ }()
+ config.mutexListeners.RLock()
+ defer config.mutexListeners.RUnlock()
+ for _, l := range config.chgListeners {
+ l.ConfigChanged(config, src)
+ }
+}
+
+// monitor periodically checks a config source for changes.
+func (config *Config) monitor(se *sourceEntry) {
+ go func(se *sourceEntry, shutdown <-chan interface{}) {
+ var src SourceMonitored
+ var ok bool
+ if src, ok = se.src.(SourceMonitored); !ok {
+ return
+ }
+ paused := false
+ last := time.Time{}
+ freq := src.GetMonitorFreq()
+ if freq <= 0 {
+ paused = true
+ freq = 10
+ last, _ = src.GetLastModified()
+ }
+ timer := time.NewTimer(freq)
+ for {
+ select {
+ case <-timer.C:
+ if !paused {
+ if latest, err := src.GetLastModified(); err != nil {
+ if config.ShouldPanicOnError() {
+ panic(fmt.Sprintf("error <%v> getting last modified for %v", err, src))
+ }
+ } else {
+ if last.Before(latest) {
+ last = latest
+ config.reloadProps(se)
+ // TODO: calc diff and provide detailed changes
+ config.onSourceChanged(src)
+ }
+ }
+ }
+ freq = src.GetMonitorFreq()
+ if freq <= 0 {
+ paused = true
+ freq = 10
+ } else {
+ paused = false
+ }
+ timer.Reset(freq)
+ case <-shutdown:
+ // stop the timer and exit
+ if !timer.Stop() {
+ <-timer.C
+ }
+ return
+ }
+ }
+ }(se, config.shutdown)
+}
+
+// reloadProps causes a Source to reload its properties.
+func (config *Config) reloadProps(se *sourceEntry) {
+ config.mutexSrc.Lock()
+ defer config.mutexSrc.Unlock()
+
+ m, err := se.src.GetProps()
+ if err != nil {
+ if config.wantPanicOnError {
+ panic(fmt.Sprintf("GetProps error for %v", se.src))
+ }
+ return
+ }
+
+ se.props = make(map[string]string)
+ for k, v := range m {
+ se.props[k] = v
+ }
+}