aboutsummaryrefslogtreecommitdiff
path: root/config.go
diff options
context:
space:
mode:
authorYaroslav de la Peña Smirnov <yps@yaroslavps.com>2023-03-26 21:17:01 +0300
committerYaroslav de la Peña Smirnov <yps@yaroslavps.com>2023-03-26 21:17:01 +0300
commit3aa790585e8c3c8cff37fc065f34989477c5bdc3 (patch)
tree037ecb9fcad12e583deacb224b234a0f6456c696 /config.go
downloadtakeoff-3aa790585e8c3c8cff37fc065f34989477c5bdc3.tar.gz
takeoff-3aa790585e8c3c8cff37fc065f34989477c5bdc3.zip
Initial commit
Diffstat (limited to 'config.go')
-rw-r--r--config.go220
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)
+ }
+}