96 lines
1.8 KiB
Go
96 lines
1.8 KiB
Go
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
|
|
}
|