This commit is contained in:
Daniel Ponte 2022-11-13 10:03:13 -05:00
parent 3981025fa4
commit 43682fab05
2 changed files with 79 additions and 40 deletions

28
pkg/storage/fs.go Normal file
View file

@ -0,0 +1,28 @@
package storage
import (
"errors"
"sync"
)
var (
ErrNoSuchKey = errors.New("no such key in store")
)
type Item interface {
sync.Locker
Dirty()
IsDirty() bool
GetData() interface{}
SetData(interface{})
ItemKey() string
}
type Store interface {
GetItem(key string, data interface{}) (Item, error)
Get(key string, data interface{}) error
Put(key string, version, minorVersion int, secretMode bool, data interface{}) (Item, error)
FlushAll() []error
Flush(key string) error
Shutdown()
}

View file

@ -2,55 +2,43 @@ package storage
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/fs" "io/fs"
"os" "os"
"path" "path"
"strings" "strings"
"sync"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
var ( var (
IndentStr = strings.Repeat(" ", 4) IndentStr = strings.Repeat(" ", 4)
ErrNoSuchKey = errors.New("no such key in store")
) )
const ( const (
SecretMode os.FileMode = 0600 SecretMode fs.FileMode = 0600
DefaultMode os.FileMode = 0644 DefaultMode fs.FileMode = 0644
) )
type Data interface {
}
type item struct { type item struct {
sync.Mutex `json:"-"`
Version int `json:"version"` Version int `json:"version"`
MinorVersion *int `json:"minor_version,omitempty"` MinorVersion *int `json:"minor_version,omitempty"`
Key string `json:"key"` Key string `json:"key"`
Data interface{} `json:"data"` Data interface{} `json:"data"`
fmode os.FileMode fmode fs.FileMode
dirty bool dirty bool
} }
type Item interface { func (i *item) Dirty() { i.Lock(); defer i.Unlock(); i.dirty = true }
Dirty() func (i *item) IsDirty() bool { i.Lock(); defer i.Unlock(); return i.dirty }
IsDirty() bool func (i *item) GetData() interface{} { i.Lock(); defer i.Unlock(); return i.Data }
GetData() interface{} func (i *item) SetData(d interface{}) { i.Lock(); defer i.Unlock(); i.Data = d; i.dirty = true }
SetData(interface{}) func (i *item) ItemKey() string { return i.Key /* key is immutable */ }
ItemKey() string
}
func (i *item) Dirty() { i.dirty = true } func (it *item) mode() fs.FileMode {
func (i *item) IsDirty() bool { return i.dirty }
func (i *item) GetData() interface{} { return i.Data }
func (i *item) SetData(d interface{}) { i.Data = d; i.Dirty() }
func (i *item) ItemKey() string { return i.Key }
func (it *item) mode() os.FileMode {
if it.fmode != 0 { if it.fmode != 0 {
return it.fmode return it.fmode
} }
@ -59,21 +47,35 @@ func (it *item) mode() os.FileMode {
} }
type fsStore struct { type fsStore struct {
fs.FS sync.RWMutex
fs fs.FS
storeRoot string storeRoot string
s map[string]*item s map[string]*item
} }
type Store interface { func (s *fsStore) get(key string) *item {
GetItem(key string, data interface{}) (Item, error) s.RLock()
Get(key string, data interface{}) error defer s.RUnlock()
Put(key string, version, minorVersion int, secretMode bool, data interface{}) (Item, error)
FlushAll() []error i, ok := s.s[key]
Flush(key string) error if !ok {
Shutdown() return nil
}
return i
}
func (s *fsStore) put(key string, it *item) {
s.Lock()
defer s.Unlock()
s.s[key] = it
} }
func (s *fsStore) persist(it *item) error { func (s *fsStore) persist(it *item) error {
it.Lock()
defer it.Unlock()
f, err := os.OpenFile(path.Join(s.storeRoot, it.Key), os.O_WRONLY|os.O_CREATE, it.mode()) f, err := os.OpenFile(path.Join(s.storeRoot, it.Key), os.O_WRONLY|os.O_CREATE, it.mode())
if err != nil { if err != nil {
return err return err
@ -93,19 +95,19 @@ func (s *fsStore) persist(it *item) error {
} }
func (s *fsStore) Dirty(key string) error { func (s *fsStore) Dirty(key string) error {
it, has := s.s[key] it := s.get(key)
if !has { if it == nil {
return ErrNoSuchKey return ErrNoSuchKey
} }
it.dirty = true it.Dirty()
return nil return nil
} }
func (s *fsStore) Flush(key string) error { func (s *fsStore) Flush(key string) error {
it, exists := s.s[key] it := s.get(key)
if !exists { if it == nil {
return ErrNoSuchKey return ErrNoSuchKey
} }
@ -113,6 +115,9 @@ func (s *fsStore) Flush(key string) error {
} }
func (s *fsStore) FlushAll() []error { func (s *fsStore) FlushAll() []error {
s.RLock()
defer s.RUnlock()
var errs []error var errs []error
for _, it := range s.s { for _, it := range s.s {
err := s.persist(it) err := s.persist(it)
@ -167,15 +172,21 @@ func (s *fsStore) Get(key string, data interface{}) error {
} }
func (s *fsStore) GetItem(key string, data interface{}) (Item, error) { func (s *fsStore) GetItem(key string, data interface{}) (Item, error) {
f, err := s.Open(key) f, err := s.fs.Open(key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
item := &item{ item := &item{
Data: data, Data: data,
fmode: fi.Mode(),
} }
d := json.NewDecoder(f) d := json.NewDecoder(f)
err = d.Decode(item) err = d.Decode(item)
@ -187,7 +198,7 @@ func (s *fsStore) GetItem(key string, data interface{}) (Item, error) {
return nil, fmt.Errorf("key mismatch '%s' != '%s'", item.Key, key) return nil, fmt.Errorf("key mismatch '%s' != '%s'", item.Key, key)
} }
s.s[key] = item s.put(key, item)
return item, nil return item, nil
} }
@ -197,7 +208,7 @@ func OpenFileStore(configRoot string) (*fsStore, error) {
stor := os.DirFS(storeRoot) stor := os.DirFS(storeRoot)
return &fsStore{ return &fsStore{
FS: stor, fs: stor,
storeRoot: storeRoot, storeRoot: storeRoot,
s: make(map[string]*item), s: make(map[string]*item),
}, nil }, nil