Pad/internal/io/pool/real/filesystem.go

108 lines
2.9 KiB
Go

package real
import (
"io"
"os"
"path/filepath"
"pad/internal/io/pool/types"
)
// RealFileSystem implements the pool.FileSystem interface using the real OS filesystem.
type RealFileSystem struct {
WorkingDir string
}
// NewRealFileSystem creates a RealFileSystem rooted at the given working directory.
// If workingDir is empty, it defaults to "/".
func NewRealFileSystem(workingDir string) *RealFileSystem {
if workingDir == "" {
workingDir = "/"
}
abs, err := filepath.Abs(workingDir)
if err != nil {
abs = workingDir
}
return &RealFileSystem{WorkingDir: abs}
}
func (fs *RealFileSystem) ReadDir(path string) ([]types.DirEntry, error) {
entries, err := os.ReadDir(filepath.Join(fs.WorkingDir, path))
if err != nil {
return nil, err
}
var res []types.DirEntry
for _, e := range entries {
res = append(res, &realDirEntry{e})
}
return res, nil
}
func (fs *RealFileSystem) DirExists(path string) bool {
info, err := os.Stat(filepath.Join(fs.WorkingDir, path))
return err == nil && info.IsDir()
}
func (fs *RealFileSystem) FileExists(path string) bool {
info, err := os.Stat(filepath.Join(fs.WorkingDir, path))
return err == nil && !info.IsDir()
}
func (fs *RealFileSystem) ReadFile(path string) ([]byte, error) {
return os.ReadFile(filepath.Join(fs.WorkingDir, path))
}
func (fs *RealFileSystem) ReadFileAt(path string, offset, size int) ([]byte, error) {
fullPath := filepath.Join(fs.WorkingDir, path)
f, err := os.Open(fullPath)
if err != nil {
return nil, err
}
defer f.Close()
buf := make([]byte, size)
n, err := f.ReadAt(buf, int64(offset))
if err != nil && err != io.EOF {
return nil, err
}
return buf[:n], nil
}
func (fs *RealFileSystem) WriteFile(path string, content []byte) error {
// Simple write for backward compatibility if needed,
// but defer to Atomic implementation.
return fs.WriteFileAtomic(path, content)
}
func (fs *RealFileSystem) WriteFileAtomic(path string, content []byte) error {
// Atomic write implementation
fullPath := filepath.Join(fs.WorkingDir, path)
// Temp file in the same directory as the target to ensure same filesystem rename
tmpPath := filepath.Join(filepath.Dir(fullPath), "."+filepath.Base(path)+".tmp")
if err := os.MkdirAll(filepath.Dir(tmpPath), 0755); err != nil {
return err
}
if err := os.WriteFile(tmpPath, content, 0644); err != nil {
return err
}
return os.Rename(tmpPath, fullPath)
}
func (fs *RealFileSystem) DeleteFile(path string) error {
return os.Remove(filepath.Join(fs.WorkingDir, path))
}
func (fs *RealFileSystem) CreateDir(path string) error {
return os.MkdirAll(filepath.Join(fs.WorkingDir, path), 0755)
}
// realDirEntry wraps os.DirEntry
type realDirEntry struct {
entry os.DirEntry
}
func (e *realDirEntry) Name() string { return e.entry.Name() }
func (e *realDirEntry) IsDir() bool { return e.entry.IsDir() }
func (e *realDirEntry) Info() (os.FileInfo, error) { return e.entry.Info() }