From 3aa790585e8c3c8cff37fc065f34989477c5bdc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Sun, 26 Mar 2023 21:17:01 +0300 Subject: Initial commit --- config.go | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 config.go (limited to 'config.go') 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) + } +} -- cgit v1.2.3