file-store/pkg/storeserver/local.go

139 lines
2.9 KiB
Go

package storeserver
import (
// "context"
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path"
"strings"
"github.com/google/uuid"
filemeta "git.keganmyers.com/terribleplan/file-store/pkg/file/meta"
"git.keganmyers.com/terribleplan/file-store/pkg/ifs"
// "git.keganmyers.com/terribleplan/file-store/pkg/proto"
)
var _ = (StoreServer)((*LocalStoreServer)(nil))
func NewLocal(dir string) StoreServer {
f := ifs.DirFS(dir)
return &LocalStoreServer{f: f}
}
type LocalStoreServer struct {
f ifs.FS
}
func (s *LocalStoreServer) fileIdPath(fileId string) (string, error) {
if err := ValidateFileId(fileId); err != nil {
return "", err
}
return path.Join(fileId[0:2], fileId[2:4], fileId[4:6], fileId[6:8], fileId), nil
}
func (s *LocalStoreServer) fileMetaPath(fileId string) (string, error) {
fileIdPath, err := s.fileIdPath(fileId)
if err != nil {
return "", err
}
return path.Join(fileIdPath, "meta.json"), nil
}
func (s *LocalStoreServer) ListFiles(fn ListFilesFn) error {
return ifs.WalkDir(s.f, "/", func(path string, d fs.DirEntry, err error) error {
if d.IsDir() && len(path) > 48 {
return ifs.SkipDir
}
if !d.IsDir() && len(path) == 48 && d.Name() == "meta.json" {
fileId := path[strings.LastIndex(path, "/"):]
if _, err := uuid.Parse(fileId); err == nil {
fn(fileId)
}
}
return nil
})
}
func (s *LocalStoreServer) ReadFile(fileId string) ([]byte, error) {
filePath, err := s.fileMetaPath(fileId)
if err != nil {
return nil, err
}
file, err := s.f.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
meta, err := filemeta.ReadBytes(file)
if err != nil {
return nil, err
}
return meta, nil
}
func (s *LocalStoreServer) WriteFile(fileId string, meta []byte) error {
dirPath, err := s.fileIdPath(fileId)
if err != nil {
return err
}
if err := s.f.MkdirAll(dirPath, 0755); err != nil {
return err
}
metaPath, err := s.fileMetaPath(fileId)
if err != nil {
return err
}
file, err := s.f.OpenFile(metaPath, os.O_WRONLY|os.O_EXCL|os.O_CREATE, 0644)
if err != nil {
if err == os.ErrExist {
existing, err := s.ReadFile(fileId)
if err != nil {
return err
}
if bytes.Equal(meta, existing) {
return nil
}
return err
}
return err
}
defer file.Close()
if _, err := file.Write(meta); err != nil {
return err
}
return nil
}
func (s *LocalStoreServer) DeleteFile(fileId string) error {
fileIdPath, err := s.fileIdPath(fileId)
if err != nil {
return err
}
return s.f.RemoveAll(fileIdPath)
}
func (s *LocalStoreServer) WriteChunk(fileId string, chunkId uint16, data io.ReadCloser) error {
fileIdPath, err := s.fileIdPath(fileId)
if err != nil {
return err
}
filePath := path.Join(fileIdPath, fmt.Sprintf("chunk.%04d", chunkId))
file, err := s.f.OpenFile(filePath, os.O_WRONLY|os.O_EXCL|os.O_CREATE, 0644)
if err != nil {
return err
}
if _, err := io.Copy(file, data); err != nil {
os.Remove(filePath)
return err
}
return nil
}