2025-07-06 22:59:08 +03:00
|
|
|
package filesystem
|
|
|
|
|
|
|
|
import (
|
2025-07-27 19:02:05 +03:00
|
|
|
"errors"
|
2025-07-06 22:59:08 +03:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2025-07-27 19:02:05 +03:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"code.uint32.ru/tiny/storage/internal/errinternal"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
fileModeDir os.FileMode = 0755
|
|
|
|
fileModeFile os.FileMode = 0644
|
2025-07-06 22:59:08 +03:00
|
|
|
)
|
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
var (
|
|
|
|
ErrInvalidKey = errinternal.ErrInvalidKey
|
|
|
|
ErrNotFound = errinternal.ErrNotFound
|
|
|
|
)
|
|
|
|
|
|
|
|
func New(path string) (*Storage, error) {
|
2025-07-06 22:59:08 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
return &Storage{Dir: abs}, nil
|
2025-07-06 22:59:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type Storage struct {
|
2025-07-27 19:02:05 +03:00
|
|
|
Dir string
|
2025-07-06 22:59:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) Save(key string, data []byte) error {
|
2025-07-27 19:02:05 +03:00
|
|
|
if err := validateKey(key); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-07-06 22:59:08 +03:00
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
path := s.getKeyPath(key)
|
|
|
|
if err := os.MkdirAll(path, fileModeDir); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.WriteFile(filepath.Join(path, key), data, fileModeFile); err != nil {
|
2025-07-06 22:59:08 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) Load(key string) ([]byte, error) {
|
2025-07-27 19:02:05 +03:00
|
|
|
if err := validateKey(key); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2025-07-06 22:59:08 +03:00
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
path := s.getKeyPath(key)
|
|
|
|
|
|
|
|
b, err := os.ReadFile(filepath.Join(path, key))
|
|
|
|
if err != nil && errors.Is(err, os.ErrNotExist) {
|
|
|
|
return nil, errors.Join(errinternal.ErrNotFound, err)
|
|
|
|
} else if err != nil {
|
2025-07-06 22:59:08 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) Delete(key string) error {
|
2025-07-27 19:02:05 +03:00
|
|
|
if err := validateKey(key); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-07-06 22:59:08 +03:00
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
path := s.getKeyPath(key)
|
|
|
|
|
|
|
|
err := os.Remove(filepath.Join(path, key))
|
|
|
|
if err != nil && errors.Is(err, os.ErrNotExist) {
|
2025-07-06 22:59:08 +03:00
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
// TODO: think of cleaning up path when no files left
|
|
|
|
// in /basedir/a/b/ after deleting key abc
|
|
|
|
|
2025-07-06 22:59:08 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
func (s *Storage) getKeyPath(key string) string {
|
|
|
|
return filepath.Join(s.Dir, getPrefixPath(key))
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateKey(key string) error {
|
|
|
|
if len([]rune(key)) < 3 {
|
|
|
|
return errors.Join(ErrInvalidKey, fmt.Errorf("key must be at least 3 characters long"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Of course windoze guys are missing the whole point, but
|
|
|
|
// let us use os-specific path separator and not ruin the whole fun
|
|
|
|
// for them :)
|
|
|
|
if strings.Contains(key, string(os.PathSeparator)) {
|
|
|
|
return errors.Join(ErrInvalidKey, fmt.Errorf("key must not contain path separator character: %s", string(os.PathSeparator)))
|
|
|
|
}
|
|
|
|
|
2025-07-06 22:59:08 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-07-27 19:02:05 +03:00
|
|
|
func getPrefixPath(key string) string {
|
|
|
|
r := []rune(key)
|
|
|
|
out := []rune{r[0], '/', r[1]}
|
|
|
|
|
|
|
|
return string(out)
|
2025-07-06 22:59:08 +03:00
|
|
|
}
|