repos / pico

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

pico / pkg / pobj / storage
Antonio Mika  ·  2025-03-12

fs.go

  1package storage
  2
  3import (
  4	"fmt"
  5	"io"
  6	"io/fs"
  7	"log/slog"
  8	"os"
  9	"path"
 10	"path/filepath"
 11	"strings"
 12	"time"
 13
 14	"github.com/picosh/pico/pkg/send/utils"
 15)
 16
 17// https://stackoverflow.com/a/32482941
 18func dirSize(path string) (int64, error) {
 19	var size int64
 20	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
 21		if err != nil {
 22			return err
 23		}
 24		if !info.IsDir() {
 25			size += info.Size()
 26		}
 27		return err
 28	})
 29
 30	return size, err
 31}
 32
 33type StorageFS struct {
 34	Dir    string
 35	Logger *slog.Logger
 36}
 37
 38var _ ObjectStorage = &StorageFS{}
 39var _ ObjectStorage = (*StorageFS)(nil)
 40
 41func NewStorageFS(logger *slog.Logger, dir string) (*StorageFS, error) {
 42	return &StorageFS{Logger: logger, Dir: dir}, nil
 43}
 44
 45func (s *StorageFS) GetBucket(name string) (Bucket, error) {
 46	dirPath := filepath.Join(s.Dir, name)
 47	bucket := Bucket{
 48		Name: name,
 49		Path: dirPath,
 50	}
 51	s.Logger.Info("get bucket", "dir", dirPath)
 52
 53	info, err := os.Stat(dirPath)
 54	if os.IsNotExist(err) {
 55		return bucket, fmt.Errorf("directory does not exist: %v %w", dirPath, err)
 56	}
 57
 58	if err != nil {
 59		return bucket, fmt.Errorf("directory error: %v %w", dirPath, err)
 60
 61	}
 62
 63	if !info.IsDir() {
 64		return bucket, fmt.Errorf("directory is a file, not a directory: %#v", dirPath)
 65	}
 66
 67	return bucket, nil
 68}
 69
 70func (s *StorageFS) UpsertBucket(name string) (Bucket, error) {
 71	s.Logger.Info("upsert bucket", "name", name)
 72	bucket, err := s.GetBucket(name)
 73	if err == nil {
 74		return bucket, nil
 75	}
 76
 77	dir := filepath.Join(s.Dir, bucket.Path)
 78	s.Logger.Info("bucket not found, creating", "dir", dir, "err", err)
 79	err = os.MkdirAll(dir, os.ModePerm)
 80	if err != nil {
 81		return bucket, err
 82	}
 83
 84	return bucket, nil
 85}
 86
 87func (s *StorageFS) GetBucketQuota(bucket Bucket) (uint64, error) {
 88	dsize, err := dirSize(bucket.Path)
 89	return uint64(dsize), err
 90}
 91
 92func (s *StorageFS) DeleteBucket(bucket Bucket) error {
 93	return os.RemoveAll(bucket.Path)
 94}
 95
 96func (s *StorageFS) GetObject(bucket Bucket, fpath string) (utils.ReadAndReaderAtCloser, *ObjectInfo, error) {
 97	objInfo := &ObjectInfo{
 98		LastModified: time.Time{},
 99		Metadata:     nil,
100		UserMetadata: map[string]string{},
101	}
102
103	dat, err := os.Open(filepath.Join(bucket.Path, fpath))
104	if err != nil {
105		return nil, objInfo, err
106	}
107
108	info, err := dat.Stat()
109	if err != nil {
110		return nil, objInfo, err
111	}
112
113	objInfo.Size = info.Size()
114	objInfo.LastModified = info.ModTime()
115	return dat, objInfo, nil
116}
117
118func (s *StorageFS) PutObject(bucket Bucket, fpath string, contents io.Reader, entry *utils.FileEntry) (string, int64, error) {
119	loc := filepath.Join(bucket.Path, fpath)
120	err := os.MkdirAll(filepath.Dir(loc), os.ModePerm)
121	if err != nil {
122		return "", 0, err
123	}
124	f, err := os.OpenFile(loc, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
125	if err != nil {
126		return "", 0, err
127	}
128
129	size, err := io.Copy(f, contents)
130	if err != nil {
131		return "", 0, err
132	}
133
134	f.Close()
135
136	if entry.Mtime > 0 {
137		uTime := time.Unix(entry.Mtime, 0)
138		_ = os.Chtimes(loc, uTime, uTime)
139	}
140
141	return loc, size, nil
142}
143
144func (s *StorageFS) DeleteObject(bucket Bucket, fpath string) error {
145	loc := filepath.Join(bucket.Path, fpath)
146	err := os.Remove(loc)
147	if err != nil {
148		return err
149	}
150
151	return nil
152}
153
154func (s *StorageFS) ListBuckets() ([]string, error) {
155	return []string{}, fmt.Errorf("not implemented")
156}
157
158func (s *StorageFS) ListObjects(bucket Bucket, dir string, recursive bool) ([]os.FileInfo, error) {
159	var fileList []os.FileInfo
160
161	fpath := path.Join(bucket.Path, dir)
162
163	info, err := os.Stat(fpath)
164	if err != nil {
165		return fileList, err
166	}
167
168	if info.IsDir() && !strings.HasSuffix(dir, "/") {
169		fileList = append(fileList, &utils.VirtualFile{
170			FName:    "",
171			FIsDir:   info.IsDir(),
172			FSize:    info.Size(),
173			FModTime: info.ModTime(),
174		})
175
176		return fileList, err
177	}
178
179	var files []fs.DirEntry
180
181	if recursive {
182		err = filepath.WalkDir(fpath, func(s string, d fs.DirEntry, err error) error {
183			if err != nil {
184				return err
185			}
186			files = append(files, d)
187			return nil
188		})
189		if err != nil {
190			fileList = append(fileList, info)
191			return fileList, nil
192		}
193	} else {
194		files, err = os.ReadDir(fpath)
195		if err != nil {
196			fileList = append(fileList, info)
197			return fileList, nil
198		}
199	}
200
201	for _, f := range files {
202		info, err := f.Info()
203		if err != nil {
204			return fileList, err
205		}
206
207		i := &utils.VirtualFile{
208			FName:    f.Name(),
209			FIsDir:   f.IsDir(),
210			FSize:    info.Size(),
211			FModTime: info.ModTime(),
212		}
213
214		fileList = append(fileList, i)
215	}
216
217	return fileList, err
218}