Additions to domain/ and workspace/ chron-note packages
This commit is contained in:
93
chron-note/internal/stores/cas/fs.go
Normal file
93
chron-note/internal/stores/cas/fs.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package cas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.michelsen.id/phill/chron/chron-note/internal/domain"
|
||||
"git.michelsen.id/phill/chron/chron-note/internal/util"
|
||||
)
|
||||
|
||||
var ErrBlobNotFound = errors.New("blob not found")
|
||||
|
||||
type FS struct {
|
||||
root string
|
||||
}
|
||||
|
||||
func NewFS(root string) *FS {
|
||||
return &FS{root: root}
|
||||
}
|
||||
|
||||
func (s *FS) Put(ctx context.Context, data []byte) (domain.BlobID, error) {
|
||||
_ = ctx
|
||||
|
||||
sum := util.Hash256(data)
|
||||
var id domain.BlobID
|
||||
copy(id[:], sum[:])
|
||||
|
||||
p := s.pathFor(id)
|
||||
|
||||
// Fast path: already exists
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
||||
return domain.BlobID{}, err
|
||||
}
|
||||
|
||||
tmp := p + ".tmp"
|
||||
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
||||
return domain.BlobID{}, err
|
||||
}
|
||||
if err := os.Rename(tmp, p); err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
// If another writer won the race, accept it.
|
||||
if _, statErr := os.Stat(p); statErr == nil {
|
||||
return id, nil
|
||||
}
|
||||
return domain.BlobID{}, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *FS) Get(ctx context.Context, id domain.BlobID) ([]byte, error) {
|
||||
_ = ctx
|
||||
p := s.pathFor(id)
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, ErrBlobNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (s *FS) Has(ctx context.Context, id domain.BlobID) (bool, error) {
|
||||
_ = ctx
|
||||
p := s.pathFor(id)
|
||||
_, err := os.Stat(p)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (s *FS) pathFor(id domain.BlobID) string {
|
||||
hexID := hex.EncodeToString(id[:])
|
||||
if len(hexID) < 4 {
|
||||
// should never happen
|
||||
return filepath.Join(s.root, fmt.Sprintf("bad-%s", hexID))
|
||||
}
|
||||
// git-style fanout
|
||||
return filepath.Join(s.root, hexID[:2], hexID[2:4], hexID)
|
||||
}
|
||||
Reference in New Issue
Block a user