package objstore
import (
"bytes"
"context"
"io"
"sort"
"strings"
"sync"
"time"
"github.com/pkg/errors"
)
var errNotFound = errors.New("inmem: object not found")
type InMemBucket struct {
mtx sync.RWMutex
objects map[string][]byte
attrs map[string]ObjectAttributes
}
func NewInMemBucket() *InMemBucket {
return &InMemBucket{
objects: map[string][]byte{},
attrs: map[string]ObjectAttributes{},
}
}
func (b *InMemBucket) Objects() map[string][]byte {
return b.objects
}
func (b *InMemBucket) Iter(_ context.Context, dir string, f func(string) error, options ...IterOption) error {
unique := map[string]struct{}{}
params := ApplyIterOptions(options...)
var dirPartsCount int
dirParts := strings.SplitAfter(dir, DirDelim)
for _, p := range dirParts {
if p == "" {
continue
}
dirPartsCount++
}
b.mtx.RLock()
for filename := range b.objects {
if !strings.HasPrefix(filename, dir) || dir == filename {
continue
}
if params.Recursive {
unique[filename] = struct{}{}
continue
}
parts := strings.SplitAfter(filename, DirDelim)
unique[strings.Join(parts[:dirPartsCount+1], "")] = struct{}{}
}
b.mtx.RUnlock()
var keys []string
for n := range unique {
keys = append(keys, n)
}
sort.Slice(keys, func(i, j int) bool {
if strings.HasSuffix(keys[i], DirDelim) && strings.HasSuffix(keys[j], DirDelim) {
return strings.Compare(keys[i], keys[j]) < 0
}
if strings.HasSuffix(keys[i], DirDelim) {
return false
}
if strings.HasSuffix(keys[j], DirDelim) {
return true
}
return strings.Compare(keys[i], keys[j]) < 0
})
for _, k := range keys {
if err := f(k); err != nil {
return err
}
}
return nil
}
func (b *InMemBucket) Get(_ context.Context, name string) (io.ReadCloser, error) {
if name == "" {
return nil, errors.New("inmem: object name is empty")
}
b.mtx.RLock()
file, ok := b.objects[name]
b.mtx.RUnlock()
if !ok {
return nil, errNotFound
}
return io.NopCloser(bytes.NewReader(file)), nil
}
func (b *InMemBucket) GetRange(_ context.Context, name string, off, length int64) (io.ReadCloser, error) {
if name == "" {
return nil, errors.New("inmem: object name is empty")
}
b.mtx.RLock()
file, ok := b.objects[name]
b.mtx.RUnlock()
if !ok {
return nil, errNotFound
}
if int64(len(file)) < off {
return io.NopCloser(bytes.NewReader(nil)), nil
}
if length == -1 {
return io.NopCloser(bytes.NewReader(file[off:])), nil
}
if length <= 0 {
return io.NopCloser(bytes.NewReader(nil)), errors.New("length cannot be smaller or equal 0")
}
if int64(len(file)) <= off+length {
length = int64(len(file)) - off
}
return io.NopCloser(bytes.NewReader(file[off : off+length])), nil
}
func (b *InMemBucket) Exists(_ context.Context, name string) (bool, error) {
b.mtx.RLock()
defer b.mtx.RUnlock()
_, ok := b.objects[name]
return ok, nil
}
func (b *InMemBucket) Attributes(_ context.Context, name string) (ObjectAttributes, error) {
b.mtx.RLock()
attrs, ok := b.attrs[name]
b.mtx.RUnlock()
if !ok {
return ObjectAttributes{}, errNotFound
}
return attrs, nil
}
func (b *InMemBucket) Upload(_ context.Context, name string, r io.Reader) error {
b.mtx.Lock()
defer b.mtx.Unlock()
body, err := io.ReadAll(r)
if err != nil {
return err
}
b.objects[name] = body
b.attrs[name] = ObjectAttributes{
Size: int64(len(body)),
LastModified: time.Now(),
}
return nil
}
func (b *InMemBucket) Delete(_ context.Context, name string) error {
b.mtx.Lock()
defer b.mtx.Unlock()
if _, ok := b.objects[name]; !ok {
return errNotFound
}
delete(b.objects, name)
delete(b.attrs, name)
return nil
}
func (b *InMemBucket) IsObjNotFoundErr(err error) bool {
return errors.Is(err, errNotFound)
}
func (b *InMemBucket) Close() error { return nil }
func (b *InMemBucket) Name() string {
return "inmem"
}