From 732bc08ceda0d6a4432d512277b77090b8ae3b79 Mon Sep 17 00:00:00 2001 From: Chris Macklin Date: Mon, 3 May 2021 14:01:06 -0700 Subject: [PATCH] config: replace defaultConfig with a thread-safe in-memory implementation --- fs/config/config.go | 2 +- fs/config/default_storage.go | 125 +++++++++++++++++++++--------- fs/config/default_storage_test.go | 42 ++++++++++ 3 files changed, 132 insertions(+), 37 deletions(-) create mode 100644 fs/config/default_storage_test.go diff --git a/fs/config/config.go b/fs/config/config.go index b7c1cce53..6fed40099 100644 --- a/fs/config/config.go +++ b/fs/config/config.go @@ -121,7 +121,7 @@ func init() { fs.ConfigFileGet = FileGetFlag fs.ConfigFileSet = SetValueAndSave configPath = makeConfigPath() - data = defaultStorage{} + data = newDefaultStorage() } // Join directory with filename, and check if exists diff --git a/fs/config/default_storage.go b/fs/config/default_storage.go index 482648336..4debac0be 100644 --- a/fs/config/default_storage.go +++ b/fs/config/default_storage.go @@ -1,61 +1,114 @@ package config -// Default config.Storage which panics with a useful error when used -type defaultStorage struct{} +import ( + "encoding/json" + "sync" +) -var noConfigStorage = "internal error: no config file system found. Did you call configfile.Install()?" - -// GetSectionList returns a slice of strings with names for all the -// sections -func (defaultStorage) GetSectionList() []string { - panic(noConfigStorage) +// defaultStorage implements config.Storage, providing in-memory config. +// Indexed by section, then key. +type defaultStorage struct { + mu sync.RWMutex + sections map[string]map[string]string } -// HasSection returns true if section exists in the config file -func (defaultStorage) HasSection(section string) bool { - panic(noConfigStorage) +func newDefaultStorage() *defaultStorage { + return &defaultStorage{ + sections: map[string]map[string]string{}, + } } -// DeleteSection removes the named section and all config from the -// config file -func (defaultStorage) DeleteSection(section string) { - panic(noConfigStorage) +// GetSectionList returns a slice of strings with names for all the sections. +func (s *defaultStorage) GetSectionList() []string { + s.mu.RLock() + defer s.mu.RUnlock() + sections := make([]string, 0, len(s.sections)) + for section := range s.sections { + sections = append(sections, section) + } + return sections } -// GetKeyList returns the keys in this section -func (defaultStorage) GetKeyList(section string) []string { - panic(noConfigStorage) +// HasSection returns true if section exists in the config. +func (s *defaultStorage) HasSection(section string) bool { + s.mu.RLock() + defer s.mu.RUnlock() + _, hasSection := s.sections[section] + return hasSection } -// GetValue returns the key in section with a found flag -func (defaultStorage) GetValue(section string, key string) (value string, found bool) { - panic(noConfigStorage) +// DeleteSection deletes the specified section. +func (s *defaultStorage) DeleteSection(section string) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.sections, section) } -// SetValue sets the value under key in section -func (defaultStorage) SetValue(section string, key string, value string) { - panic(noConfigStorage) +// GetKeyList returns the keys in this section. +func (s *defaultStorage) GetKeyList(section string) []string { + s.mu.RLock() + defer s.mu.RUnlock() + theSection := s.sections[section] + keys := make([]string, 0, len(theSection)) + for key := range theSection { + keys = append(keys, key) + } + return keys } -// DeleteKey removes the key under section -func (defaultStorage) DeleteKey(section string, key string) bool { - panic(noConfigStorage) +// GetValue returns the key in section with a found flag. +func (s *defaultStorage) GetValue(section string, key string) (value string, found bool) { + s.mu.RLock() + defer s.mu.RUnlock() + theSection, hasSection := s.sections[section] + if !hasSection { + return "", false + } + value, hasValue := theSection[key] + return value, hasValue } -// Load the config from permanent storage -func (defaultStorage) Load() error { - panic(noConfigStorage) +func (s *defaultStorage) SetValue(section string, key string, value string) { + s.mu.Lock() + defer s.mu.Unlock() + theSection, hasSection := s.sections[section] + if !hasSection { + theSection = map[string]string{} + s.sections[section] = theSection + } + theSection[key] = value } -// Save the config to permanent storage -func (defaultStorage) Save() error { - panic(noConfigStorage) +func (s *defaultStorage) DeleteKey(section string, key string) bool { + s.mu.Lock() + defer s.mu.Unlock() + theSection, hasSection := s.sections[section] + if !hasSection { + return false + } + _, hasKey := theSection[key] + if !hasKey { + return false + } + delete(s.sections[section], key) + return true +} + +func (s *defaultStorage) Load() error { + return nil +} + +func (s *defaultStorage) Save() error { + return nil } // Serialize the config into a string -func (defaultStorage) Serialize() (string, error) { - panic(noConfigStorage) +func (s *defaultStorage) Serialize() (string, error) { + s.mu.RLock() + defer s.mu.RUnlock() + j, err := json.Marshal(s.sections) + return string(j), err } // Check the interface is satisfied -var _ Storage = defaultStorage{} +var _ Storage = newDefaultStorage() diff --git a/fs/config/default_storage_test.go b/fs/config/default_storage_test.go new file mode 100644 index 000000000..4f1238c55 --- /dev/null +++ b/fs/config/default_storage_test.go @@ -0,0 +1,42 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDefaultStorage(t *testing.T) { + a := assert.New(t) + + ds := newDefaultStorage() + + section := "test" + key := "key" + val := "something" + ds.SetValue(section, key, val) + ds.SetValue("some other section", key, val) + + v, hasVal := ds.GetValue(section, key) + a.True(hasVal) + a.Equal(val, v) + + a.ElementsMatch([]string{section, "some other section"}, ds.GetSectionList()) + a.True(ds.HasSection(section)) + a.False(ds.HasSection("nope")) + + a.Equal([]string{key}, ds.GetKeyList(section)) + + _, err := ds.Serialize() + a.NoError(err) + + a.True(ds.DeleteKey(section, key)) + a.False(ds.DeleteKey(section, key)) + a.False(ds.DeleteKey("not there", key)) + + _, hasVal = ds.GetValue(section, key) + a.False(hasVal) + + ds.DeleteSection(section) + a.False(ds.HasSection(section)) +}