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}