diff options
author | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2023-03-26 21:17:01 +0300 |
---|---|---|
committer | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2023-03-26 21:17:01 +0300 |
commit | 3aa790585e8c3c8cff37fc065f34989477c5bdc3 (patch) | |
tree | 037ecb9fcad12e583deacb224b234a0f6456c696 /config.go | |
download | takeoff-3aa790585e8c3c8cff37fc065f34989477c5bdc3.tar.gz takeoff-3aa790585e8c3c8cff37fc065f34989477c5bdc3.zip |
Initial commit
Diffstat (limited to 'config.go')
-rw-r--r-- | config.go | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/config.go b/config.go new file mode 100644 index 0000000..7164e93 --- /dev/null +++ b/config.go @@ -0,0 +1,220 @@ +package main + +import ( + "log" + "os" + "path" + "strings" + "sync/atomic" + + "github.com/fsnotify/fsnotify" +) + +type Lang struct { + Code string + Name string +} + +// Wrapper over atomic.Value; contains []string +type atomicStringSlice struct { + v atomic.Value +} +// Wrapper over atomic.Value; contains []Lang +type atomicLangSlice struct { + v atomic.Value +} + +// A config that is just a list of lines of text +type lineListConfig struct { + path string + items atomicStringSlice +} + +// A config that is list of two-column lines sepparated by a tab character +type langListConfig struct { + path string + langs atomicLangSlice +} + +// Interface for all types of configs that can be updated/reloaded +type updatableConfig interface { + update() error +} + +// The data type that will watch for changes in some updatableConfig types and +// call their update() methods on changes +type configWatcher struct { + lists map[string]updatableConfig + watcher *fsnotify.Watcher + dirpath string +} + +var watcher configWatcher + +func (v *atomicStringSlice) Load() []string { + return v.v.Load().([]string) +} + +func (v *atomicStringSlice) Store(val []string) { + v.v.Store(val) +} + +func (v *atomicLangSlice) Load() []Lang { + return v.v.Load().([]Lang) +} + +func (v *atomicLangSlice) Store(val []Lang) { + v.v.Store(val) +} + +// Re-read the file of this and update the items list. +func (c *lineListConfig) update() error { + data, err := os.ReadFile(c.path) + if err != nil { + return err + } + + // Cut the string into substrings line by line omitting empty lines. + s := string(data) + sep := "\n" + items := make([]string, 0, 8) + for { + if i := strings.Index(s, sep); i >= 0 { + sub := s[:i] + if len(sub) > 0 { + items = append(items, sub) + } + s = s[i+len(sep):] + continue + } + break + } + c.items.Store(items) + + return nil +} + +// Return the slice of strings atomically +func (c *lineListConfig) getList() []string { + return c.items.Load() +} + +// Re-read the file of this and update the items list. +func (c *langListConfig) update() error { + data, err := os.ReadFile(c.path) + if err != nil { + return err + } + + // Cut the string into substring line by line omitting empty lines, and + // sepparating the code and the name of the language. + s := string(data) + sep := "\n" + langs := make([]Lang, 0, 8) + for { + if i := strings.Index(s, sep); i >= 0 { + sub := s[:i] + if len(sub) > 0 { + cols := strings.SplitN(sub, "\t", 2) + if len(cols) < 2 { + log.Println("WARNING: bad language format:", sub) + } else { + langs = append(langs, Lang{ cols[0], cols[1] }) + } + } + s = s[i+len(sep):] + continue + } + break + } + c.langs.Store(langs) + + return nil +} + +// Return the slice of strings atomically +func (c *langListConfig) getList() []Lang { + return c.langs.Load() +} + +// Make a new lineListConfig from the file that is inside the configWatcher dir +// with the name give in this method. The name of the file will be automatically +// watched by the configWatcher. Returns a new instance of a lineListConfig if +// there were no errors. +func newLineListConfig(name string) (*lineListConfig, error) { + var err error + path := path.Join(watcher.dirpath, name) + + c := &lineListConfig{path: path} + err = c.update() + watcher.lists[path] = c + + return c, err +} + +// Make a new langListConfig; the same story as with lineListConfig +func newLangListConfig(name string) (*langListConfig, error) { + var err error + path := path.Join(watcher.dirpath, name) + + c := &langListConfig{path: path} + err = c.update() + watcher.lists[path] = c + + return c, err +} + +// Initialize and start the config watcher. +func startConfigWatcher(path string) error { + var err error + + watcher.lists = make(map[string]updatableConfig) + watcher.dirpath = path + + watcher.watcher, err = fsnotify.NewWatcher() + if err != nil { + return err + } + err = watcher.watcher.Add(path) + if err != nil { + return err + } + + go func() { + for { + select { + case e, ok := <-watcher.watcher.Events: + if !ok { + return + } + if e.Has(fsnotify.Write) { + cl, ok := watcher.lists[e.Name] + if ok { + if err := cl.update(); err != nil { + log.Println( + "read error: couldn't update", e.Name, ":", err, + ) + break + } + log.Println("reloaded config list from file:", e.Name) + } + } + case err, ok := <-watcher.watcher.Errors: + if !ok { + return + } + log.Println("watcher error:", err) + } + } + }() + + return err +} + +// Stop the configList watcher. +func stopConfigWatcher() { + err := watcher.watcher.Close() + if err != nil { + log.Println("watcher error: on close:", err) + } +} |