package storage import ( "crypto/sha256" "encoding/hex" "errors" "io" "os" "path/filepath" ) type FSStore struct { root string maxObjectB int64 } func NewFSStore(root string, maxKB int) (*FSStore, error) { if root == "" { root = "./data/objects" } if err := os.MkdirAll(root, 0o755); err != nil { return nil, err } return &FSStore{root: root, maxObjectB: int64(maxKB) * 1024}, nil } func (s *FSStore) Put(r io.Reader) (string, int64, error) { h := sha256.New() tmp := filepath.Join(s.root, ".tmp") _ = os.MkdirAll(tmp, 0o755) f, err := os.CreateTemp(tmp, "obj-*") if err != nil { return "", 0, err } defer f.Close() var n int64 buf := make([]byte, 32*1024) for { m, er := r.Read(buf) if m > 0 { n += int64(m) if s.maxObjectB > 0 && n > s.maxObjectB { return "", 0, errors.New("object too large") } _, _ = h.Write(buf[:m]) if _, werr := f.Write(buf[:m]); werr != nil { return "", 0, werr } } if er == io.EOF { break } if er != nil { return "", 0, er } } sum := hex.EncodeToString(h.Sum(nil)) dst := filepath.Join(s.root, sum[:2], sum[2:4], sum) if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return "", 0, err } if err := os.Rename(f.Name(), dst); err != nil { return "", 0, err } return sum, n, nil } func (s *FSStore) pathFor(hash string) string { return filepath.Join(s.root, hash[:2], hash[2:4], hash) } func (s *FSStore) Get(hash string) (string, error) { if len(hash) < 4 { return "", os.ErrNotExist } p := s.pathFor(hash) if _, err := os.Stat(p); err != nil { return "", err } return p, nil } func (s *FSStore) Delete(hash string) error { if len(hash) < 4 { return os.ErrNotExist } p := s.pathFor(hash) if err := os.Remove(p); err != nil { return err } _ = os.Remove(filepath.Dir(p)) _ = os.Remove(filepath.Dir(filepath.Dir(p))) return nil }