repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / pobj / storage
Eric Bower  ·  2025-04-18

memory.go

  1package storage
  2
  3import (
  4	"fmt"
  5	"io"
  6	"os"
  7	"path/filepath"
  8	"strings"
  9	"sync"
 10	"time"
 11
 12	"github.com/picosh/pico/pkg/send/utils"
 13)
 14
 15type StorageMemory struct {
 16	storage map[string]map[string]string
 17	mu      sync.RWMutex
 18}
 19
 20var _ ObjectStorage = &StorageMemory{}
 21var _ ObjectStorage = (*StorageMemory)(nil)
 22
 23func NewStorageMemory(st map[string]map[string]string) (*StorageMemory, error) {
 24	return &StorageMemory{
 25		storage: st,
 26	}, nil
 27}
 28
 29func (s *StorageMemory) GetBucket(name string) (Bucket, error) {
 30	s.mu.RLock()
 31	defer s.mu.RUnlock()
 32
 33	bucket := Bucket{
 34		Name: name,
 35		Path: name,
 36	}
 37
 38	_, ok := s.storage[name]
 39	if !ok {
 40		return bucket, fmt.Errorf("bucket does not exist")
 41	}
 42
 43	return bucket, nil
 44}
 45
 46func (s *StorageMemory) UpsertBucket(name string) (Bucket, error) {
 47	bucket, err := s.GetBucket(name)
 48	if err == nil {
 49		return bucket, nil
 50	}
 51
 52	s.mu.Lock()
 53	defer s.mu.Unlock()
 54
 55	s.storage[name] = map[string]string{}
 56	return bucket, nil
 57}
 58
 59func (s *StorageMemory) GetBucketQuota(bucket Bucket) (uint64, error) {
 60	s.mu.RLock()
 61	defer s.mu.RUnlock()
 62
 63	objects := s.storage[bucket.Path]
 64	size := 0
 65	for _, val := range objects {
 66		size += len([]byte(val))
 67	}
 68	return uint64(size), nil
 69}
 70
 71func (s *StorageMemory) DeleteBucket(bucket Bucket) error {
 72	s.mu.Lock()
 73	defer s.mu.Unlock()
 74
 75	delete(s.storage, bucket.Path)
 76	return nil
 77}
 78
 79func (s *StorageMemory) GetObject(bucket Bucket, fpath string) (utils.ReadAndReaderAtCloser, *ObjectInfo, error) {
 80	s.mu.RLock()
 81	defer s.mu.RUnlock()
 82
 83	if !strings.HasPrefix(fpath, "/") {
 84		fpath = "/" + fpath
 85	}
 86
 87	objInfo := &ObjectInfo{
 88		LastModified: time.Time{},
 89		Metadata:     nil,
 90	}
 91
 92	dat, ok := s.storage[bucket.Path][fpath]
 93	if !ok {
 94		return nil, objInfo, fmt.Errorf("object does not exist: %s", fpath)
 95	}
 96
 97	objInfo.Size = int64(len([]byte(dat)))
 98	reader := utils.NopReadAndReaderAtCloser(strings.NewReader(dat))
 99	return reader, objInfo, nil
100}
101
102func (s *StorageMemory) PutObject(bucket Bucket, fpath string, contents io.Reader, entry *utils.FileEntry) (string, int64, error) {
103	s.mu.Lock()
104	defer s.mu.Unlock()
105
106	d, err := io.ReadAll(contents)
107	if err != nil {
108		return "", 0, err
109	}
110
111	s.storage[bucket.Path][fpath] = string(d)
112	return fmt.Sprintf("%s%s", bucket.Path, fpath), int64(len(d)), nil
113}
114
115func (s *StorageMemory) DeleteObject(bucket Bucket, fpath string) error {
116	s.mu.Lock()
117	defer s.mu.Unlock()
118
119	delete(s.storage[bucket.Path], fpath)
120	return nil
121}
122
123func (s *StorageMemory) ListBuckets() ([]string, error) {
124	s.mu.RLock()
125	defer s.mu.RUnlock()
126
127	buckets := []string{}
128	for key := range s.storage {
129		buckets = append(buckets, key)
130	}
131	return buckets, nil
132}
133
134func (s *StorageMemory) ListObjects(bucket Bucket, dir string, recursive bool) ([]os.FileInfo, error) {
135	s.mu.RLock()
136	defer s.mu.RUnlock()
137
138	var fileList []os.FileInfo
139
140	resolved := dir
141
142	if !strings.HasPrefix(resolved, "/") {
143		resolved = "/" + resolved
144	}
145
146	objects := s.storage[bucket.Path]
147	// dir is actually an object
148	oval, ok := objects[resolved]
149	if ok {
150		fileList = append(fileList, &utils.VirtualFile{
151			FName:    filepath.Base(resolved),
152			FIsDir:   false,
153			FSize:    int64(len([]byte(oval))),
154			FModTime: time.Time{},
155		})
156		return fileList, nil
157	}
158
159	for key, val := range objects {
160		if !strings.HasPrefix(key, resolved) {
161			continue
162		}
163
164		rep := strings.Replace(key, resolved, "", 1)
165		fdir := filepath.Dir(rep)
166		fname := filepath.Base(rep)
167		paths := strings.Split(fdir, "/")
168
169		if fdir == "/" {
170			ffname := filepath.Base(resolved)
171			fileList = append(fileList, &utils.VirtualFile{
172				FName:  ffname,
173				FIsDir: true,
174			})
175		}
176
177		for _, p := range paths {
178			if p == "" || p == "/" || p == "." {
179				continue
180			}
181			fileList = append(fileList, &utils.VirtualFile{
182				FName:  p,
183				FIsDir: true,
184			})
185		}
186
187		trimRes := strings.TrimSuffix(resolved, "/")
188		dirKey := filepath.Dir(key)
189		if recursive {
190			fileList = append(fileList, &utils.VirtualFile{
191				FName:    fname,
192				FIsDir:   false,
193				FSize:    int64(len([]byte(val))),
194				FModTime: time.Time{},
195			})
196		} else if resolved == dirKey || trimRes == dirKey {
197			fileList = append(fileList, &utils.VirtualFile{
198				FName:    fname,
199				FIsDir:   false,
200				FSize:    int64(len([]byte(val))),
201				FModTime: time.Time{},
202			})
203		}
204	}
205
206	return fileList, nil
207}