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 }