blasphem/pkg/storage/storage.go

221 lines
3.8 KiB
Go
Raw Normal View History

2022-10-02 14:39:55 -04:00
package storage
import (
2022-10-25 00:16:29 -04:00
"encoding/json"
"fmt"
2022-10-02 14:39:55 -04:00
"io/fs"
2022-11-13 09:05:09 -05:00
"os"
"path"
"strings"
2022-11-13 10:03:13 -05:00
"sync"
2022-11-13 09:05:09 -05:00
"github.com/rs/zerolog/log"
)
var (
2022-11-13 11:55:10 -05:00
IndentStr = strings.Repeat(" ", 2)
2022-11-13 09:05:09 -05:00
)
const (
2022-11-13 10:03:13 -05:00
SecretMode fs.FileMode = 0600
DefaultMode fs.FileMode = 0644
2022-10-02 14:39:55 -04:00
)
2022-11-13 09:05:09 -05:00
type item struct {
2022-11-13 10:03:13 -05:00
sync.Mutex `json:"-"`
2022-11-13 09:05:52 -05:00
Version int `json:"version"`
MinorVersion *int `json:"minor_version,omitempty"`
Key string `json:"key"`
Data interface{} `json:"data"`
2022-11-13 09:05:09 -05:00
2022-11-13 10:03:13 -05:00
fmode fs.FileMode
2022-11-13 09:05:09 -05:00
dirty bool
}
2022-11-13 10:03:13 -05:00
func (i *item) Dirty() { i.Lock(); defer i.Unlock(); i.dirty = true }
func (i *item) IsDirty() bool { i.Lock(); defer i.Unlock(); return i.dirty }
func (i *item) GetData() interface{} { i.Lock(); defer i.Unlock(); return i.Data }
func (i *item) SetData(d interface{}) { i.Lock(); defer i.Unlock(); i.Data = d; i.dirty = true }
func (i *item) ItemKey() string { return i.Key /* key is immutable */ }
2022-11-13 09:05:09 -05:00
2022-11-13 10:03:13 -05:00
func (it *item) mode() fs.FileMode {
2022-11-13 09:05:09 -05:00
if it.fmode != 0 {
return it.fmode
}
return SecretMode
}
type fsStore struct {
2022-11-13 10:03:13 -05:00
sync.RWMutex
fs fs.FS
2022-11-13 09:05:09 -05:00
storeRoot string
2022-11-13 09:05:52 -05:00
s map[string]*item
2022-10-02 14:39:55 -04:00
}
2022-11-13 10:03:13 -05:00
func (s *fsStore) get(key string) *item {
s.RLock()
defer s.RUnlock()
i, ok := s.s[key]
if !ok {
return nil
}
return i
}
func (s *fsStore) put(key string, it *item) {
s.Lock()
defer s.Unlock()
s.s[key] = it
2022-10-25 00:16:29 -04:00
}
2022-11-13 09:05:09 -05:00
func (s *fsStore) persist(it *item) error {
2022-11-13 10:03:13 -05:00
it.Lock()
defer it.Unlock()
2022-11-13 11:55:10 -05:00
f, err := os.OpenFile(path.Join(s.storeRoot, it.Key), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, it.mode())
2022-10-25 00:16:29 -04:00
if err != nil {
return err
}
defer f.Close()
2022-11-13 09:05:09 -05:00
enc := json.NewEncoder(f)
enc.SetIndent("", IndentStr)
err = enc.Encode(it)
if err == nil {
it.dirty = false
}
return err
}
func (s *fsStore) Dirty(key string) error {
2022-11-13 10:03:13 -05:00
it := s.get(key)
if it == nil {
2022-11-13 09:05:09 -05:00
return ErrNoSuchKey
}
2022-11-13 10:03:13 -05:00
it.Dirty()
2022-11-13 09:05:09 -05:00
return nil
}
func (s *fsStore) Flush(key string) error {
2022-11-13 10:03:13 -05:00
it := s.get(key)
if it == nil {
2022-11-13 09:05:09 -05:00
return ErrNoSuchKey
}
return s.persist(it)
}
func (s *fsStore) FlushAll() []error {
2022-11-13 10:03:13 -05:00
s.RLock()
defer s.RUnlock()
2022-11-13 09:05:09 -05:00
var errs []error
for _, it := range s.s {
err := s.persist(it)
if err != nil {
errs = append(errs, fmt.Errorf("store key %s: %w", it.Key, err))
}
}
return errs
}
func (s *fsStore) Shutdown() {
errs := s.FlushAll()
if errs != nil {
log.Error().Errs("errors", errs).Msg("errors persisting store")
}
}
// Put puts an item into the store.
// NB: Any user of a previous item with this key will now have a dangling reference that will not be persisted.
// It is up to consumers to coordinate against this case!
func (s *fsStore) Put(key string, version, minorVersion int, secretMode bool, data interface{}) (Item, error) {
var mv *int
if minorVersion != 0 {
mv = &minorVersion
}
mode := DefaultMode
if secretMode {
mode = SecretMode
}
it := &item{
2022-11-13 09:05:52 -05:00
Version: version,
2022-11-13 09:05:09 -05:00
MinorVersion: mv,
2022-11-13 09:05:52 -05:00
Key: key,
Data: data,
2022-11-13 09:05:09 -05:00
fmode: mode,
dirty: true,
}
s.s[key] = it
return it, s.persist(it)
}
func (s *fsStore) Get(key string, data interface{}) error {
_, err := s.GetItem(key, data)
return err
}
func (s *fsStore) GetItem(key string, data interface{}) (Item, error) {
2022-11-13 11:55:10 -05:00
exists := s.get(key)
if exists != nil {
return exists, ErrKeyExists
}
2022-11-13 10:03:13 -05:00
f, err := s.fs.Open(key)
2022-11-13 09:05:09 -05:00
if err != nil {
return nil, err
}
defer f.Close()
2022-11-13 10:03:13 -05:00
fi, err := f.Stat()
if err != nil {
return nil, err
}
2022-11-13 09:05:09 -05:00
item := &item{
2022-11-13 10:03:13 -05:00
Data: data,
fmode: fi.Mode(),
2022-10-25 00:16:29 -04:00
}
d := json.NewDecoder(f)
2022-11-13 09:05:09 -05:00
err = d.Decode(item)
2022-10-25 00:16:29 -04:00
if err != nil {
2022-11-13 09:05:09 -05:00
return nil, err
2022-10-25 00:16:29 -04:00
}
if item.Key != key {
2022-11-13 09:05:09 -05:00
return nil, fmt.Errorf("key mismatch '%s' != '%s'", item.Key, key)
2022-10-25 00:16:29 -04:00
}
2022-11-13 10:03:13 -05:00
s.put(key, item)
2022-11-13 09:05:09 -05:00
return item, nil
2022-10-25 00:16:29 -04:00
}
2022-11-13 09:05:09 -05:00
func OpenFileStore(configRoot string) (*fsStore, error) {
storeRoot := path.Join(configRoot, ".storage")
stor := os.DirFS(storeRoot)
2022-10-02 14:39:55 -04:00
2022-11-13 09:05:09 -05:00
return &fsStore{
2022-11-13 10:03:13 -05:00
fs: stor,
2022-11-13 09:05:09 -05:00
storeRoot: storeRoot,
2022-11-13 09:05:52 -05:00
s: make(map[string]*item),
2022-11-13 09:05:09 -05:00
}, nil
2022-10-02 14:39:55 -04:00
}