This commit is contained in:
Dmitry Fedotov
2025-07-06 22:59:08 +03:00
commit d85dca2195
6 changed files with 257 additions and 0 deletions

12
go.mod Normal file
View File

@@ -0,0 +1,12 @@
module code.uint32.ru/tiny/objstore
go 1.24
require (
github.com/nats-io/nats.go v1.41.2
github.com/klauspost/compress v1.18.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.32.0 // indirect
)

22
go.sum Normal file
View File

@@ -0,0 +1,22 @@
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nats.go v1.41.2 h1:5UkfLAtu/036s99AhFRlyNDI1Ieylb36qbGjJzHixos=
github.com/nats-io/nats.go v1.41.2/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View File

@@ -0,0 +1,71 @@
package filesystem
import (
"fmt"
"os"
"path/filepath"
)
func Open(path string) (*Storage, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, fmt.Errorf("provided path %s is not a directory", path)
}
abs, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("could not tarnslate %s to absolute path", path)
}
return &Storage{prefix: abs}, nil
}
type Storage struct {
prefix string
}
func (s *Storage) Save(key string, data []byte) error {
path := s.toAbs(key)
if err := os.WriteFile(path, data, 0664); err != nil {
return err
}
return nil
}
func (s *Storage) Load(key string) ([]byte, error) {
path := s.toAbs(key)
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return b, nil
}
func (s *Storage) Delete(key string) error {
path := s.toAbs(key)
err := os.Remove(path)
if err != nil && os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}
return nil
}
func (s *Storage) Close() error {
return nil
}
func (s *Storage) toAbs(path string) string {
return filepath.Join(s.prefix, path)
}

View File

@@ -0,0 +1,45 @@
package filesystem
import (
"bytes"
"os"
"testing"
)
func TestStorageMethods(t *testing.T) {
st, err := Open("./testdata")
if err != nil {
t.Fatal(err)
}
name := "mytestfile"
data := []byte("contents of my test file")
defer os.Remove(name) // just in case
if err := st.Save(name, data); err != nil {
t.Fatal(err)
}
if err := st.Save(name, data); err != nil {
t.Errorf("rewrite operatoin failed: %v", err)
}
b, err := st.Load(name)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, b) {
t.Error("loaded file differs from original")
}
if err := st.Delete(name); err != nil {
t.Errorf("delete failed: %v", err)
}
if err := st.Delete(name); err != nil {
t.Errorf("delete of non-existent failed: %v", err)
}
}

View File

@@ -0,0 +1,69 @@
package natsobj
import (
"github.com/nats-io/nats.go"
)
type Storage struct {
store nats.ObjectStore
conn *nats.Conn
}
func Open(bucket, url string) (*Storage, error) {
nc, err := nats.Connect(url)
if err != nil {
return nil, err
}
js, err := nc.JetStream()
if err != nil {
return nil, err
}
cfg := &nats.ObjectStoreConfig{
Bucket: bucket,
Description: "microkv bucket",
MaxBytes: -1,
Storage: nats.FileStorage,
Compression: true,
}
store, err := js.CreateObjectStore(cfg)
if err != nil {
return nil, err
}
st := &Storage{store: store, conn: nc}
return st, nil
}
func (n *Storage) Save(key string, data []byte) error {
if _, err := n.store.PutBytes(key, data); err != nil {
return err
}
return nil
}
func (n *Storage) Load(key string) ([]byte, error) {
b, err := n.store.GetBytes(key)
if err != nil {
return nil, err
}
return b, nil
}
func (n *Storage) Delete(key string) error {
if err := n.store.Delete(key); err != nil {
return err
}
return nil
}
func (n *Storage) Close() error {
n.conn.Close()
return nil
}

38
storage.go Normal file
View File

@@ -0,0 +1,38 @@
package microkv
import (
"code.uint32.ru/tiny/objstore/internal/filesystem"
"code.uint32.ru/tiny/objstore/internal/natsobj"
)
var (
_ Storage = (*natsobj.Storage)(nil)
_ Storage = (*filesystem.Storage)(nil)
)
// Storage is a very basic object store.
type Storage interface {
// Save puts file with name 'key' into the store. If a file with such name
// already exists it gets overwritten.
Save(key string, data []byte) error
// Load returns contents of file named 'key'.
Load(key string) ([]byte, error)
// Delete removes file named 'key' from the store.
// If such file does not exist Delete returns nil.
Delete(key string) error
// Close must be called when you're done working with Storage.
Close() error
}
// NewNats connects to NATS messaging system and tries to create
// a new object storage with name 'bucket'. The returned Storage
// uses the created bucket as underlying physical store.
func NewNats(bucket string, url string) (Storage, error) {
return natsobj.Open(bucket, url)
}
// NewFS established a key/value within the directory 'path'
// and uses is as underlying physical store.
func NewFS(path string) (Storage, error) {
return filesystem.Open(path)
}