166 lines
3.2 KiB
Go
166 lines
3.2 KiB
Go
|
package fs
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
|
||
|
"forge.cadoles.com/arcad/edge/pkg/storage/driver/cache/lfu"
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
type Store[K comparable, V any] struct {
|
||
|
baseDir string
|
||
|
getPath GetPathFunc[K]
|
||
|
marshalValue MarshalValueFunc[V]
|
||
|
unmarshalValue UnmarshalValueFunc[V]
|
||
|
}
|
||
|
|
||
|
// Delete implements Store.
|
||
|
func (s *Store[K, V]) Delete(key K) error {
|
||
|
path, err := s.getEntryPath(key)
|
||
|
if err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Get implements Store.
|
||
|
func (s *Store[K, V]) Get(key K) (V, error) {
|
||
|
path, err := s.getEntryPath(key)
|
||
|
if err != nil {
|
||
|
return *new(V), errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
value, err := s.readValue(path)
|
||
|
if err != nil {
|
||
|
if errors.Is(err, os.ErrNotExist) {
|
||
|
return *new(V), errors.WithStack(lfu.ErrNotFound)
|
||
|
}
|
||
|
|
||
|
return *new(V), errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return value, nil
|
||
|
}
|
||
|
|
||
|
// Set implements Store.
|
||
|
func (s *Store[K, V]) Set(key K, value V) error {
|
||
|
path, err := s.getEntryPath(key)
|
||
|
if err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
if err := s.writeValue(path, value); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *Store[K, V]) Clear() error {
|
||
|
if err := os.RemoveAll(s.baseDir); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *Store[K, V]) getEntryPath(k K) (string, error) {
|
||
|
path, err := s.getPath(k)
|
||
|
if err != nil {
|
||
|
return "", errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
path = append([]string{s.baseDir}, path...)
|
||
|
return filepath.Join(path...), nil
|
||
|
}
|
||
|
|
||
|
func (s *Store[K, V]) writeValue(path string, value V) error {
|
||
|
fi, err := os.Stat(path)
|
||
|
if err == nil && !fi.Mode().IsRegular() {
|
||
|
return fmt.Errorf("%s already exists and is not a regular file", path)
|
||
|
}
|
||
|
|
||
|
dir := filepath.Dir(path)
|
||
|
|
||
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
f, err := os.CreateTemp(dir, filepath.Base(path)+".tmp")
|
||
|
if err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
tmpName := f.Name()
|
||
|
defer func() {
|
||
|
if err != nil {
|
||
|
f.Close()
|
||
|
os.Remove(tmpName)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
reader, err := s.marshalValue(value)
|
||
|
if err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
if _, err := io.Copy(f, reader); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
if err := f.Sync(); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
if err := f.Close(); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
if err := os.Rename(tmpName, path); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *Store[K, V]) readValue(path string) (V, error) {
|
||
|
file, err := os.Open(path)
|
||
|
if err != nil {
|
||
|
return *new(V), errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
defer func() {
|
||
|
if err := file.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||
|
panic(errors.WithStack(err))
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
value, err := s.unmarshalValue(file)
|
||
|
|
||
|
if err != nil {
|
||
|
return *new(V), errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return value, nil
|
||
|
}
|
||
|
|
||
|
func NewStore[K comparable, V any](baseDir string, funcs ...OptionsFunc[K, V]) *Store[K, V] {
|
||
|
opts := DefaultOptions[K, V](funcs...)
|
||
|
return &Store[K, V]{
|
||
|
baseDir: baseDir,
|
||
|
getPath: opts.GetPath,
|
||
|
unmarshalValue: opts.UnmarshalValue,
|
||
|
marshalValue: opts.MarshalValue,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var _ lfu.Store[string, int] = &Store[string, int]{}
|