Clean up the client.go file that had duplicate method declarations from a bad cherry-pick merge. Now has 127 lines of simple, clean code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
127 lines
2.9 KiB
Go
127 lines
2.9 KiB
Go
// Package local provides a local filesystem implementation of the io.Medium interface.
|
|
package local
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Medium is a local filesystem storage backend.
|
|
type Medium struct {
|
|
root string
|
|
}
|
|
|
|
// New creates a new local Medium rooted at the given directory.
|
|
// Pass "/" for full filesystem access, or a specific path to sandbox.
|
|
func New(root string) (*Medium, error) {
|
|
abs, err := filepath.Abs(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Medium{root: abs}, nil
|
|
}
|
|
|
|
// path sanitizes and returns the full path.
|
|
// Replaces .. with . to prevent traversal, then joins with root.
|
|
func (m *Medium) path(p string) string {
|
|
if p == "" {
|
|
return m.root
|
|
}
|
|
clean := strings.ReplaceAll(p, "..", ".")
|
|
if filepath.IsAbs(clean) {
|
|
return filepath.Clean(clean)
|
|
}
|
|
return filepath.Join(m.root, clean)
|
|
}
|
|
|
|
// Read returns file contents as string.
|
|
func (m *Medium) Read(p string) (string, error) {
|
|
data, err := os.ReadFile(m.path(p))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// Write saves content to file, creating parent directories as needed.
|
|
func (m *Medium) Write(p, content string) error {
|
|
full := m.path(p)
|
|
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(full, []byte(content), 0644)
|
|
}
|
|
|
|
// EnsureDir creates directory if it doesn't exist.
|
|
func (m *Medium) EnsureDir(p string) error {
|
|
return os.MkdirAll(m.path(p), 0755)
|
|
}
|
|
|
|
// IsDir returns true if path is a directory.
|
|
func (m *Medium) IsDir(p string) bool {
|
|
if p == "" {
|
|
return false
|
|
}
|
|
info, err := os.Stat(m.path(p))
|
|
return err == nil && info.IsDir()
|
|
}
|
|
|
|
// IsFile returns true if path is a regular file.
|
|
func (m *Medium) IsFile(p string) bool {
|
|
if p == "" {
|
|
return false
|
|
}
|
|
info, err := os.Stat(m.path(p))
|
|
return err == nil && info.Mode().IsRegular()
|
|
}
|
|
|
|
// Exists returns true if path exists.
|
|
func (m *Medium) Exists(p string) bool {
|
|
_, err := os.Stat(m.path(p))
|
|
return err == nil
|
|
}
|
|
|
|
// List returns directory entries.
|
|
func (m *Medium) List(p string) ([]fs.DirEntry, error) {
|
|
return os.ReadDir(m.path(p))
|
|
}
|
|
|
|
// Stat returns file info.
|
|
func (m *Medium) Stat(p string) (fs.FileInfo, error) {
|
|
return os.Stat(m.path(p))
|
|
}
|
|
|
|
// Delete removes a file or empty directory.
|
|
func (m *Medium) Delete(p string) error {
|
|
full := m.path(p)
|
|
if len(full) < 3 {
|
|
return nil
|
|
}
|
|
return os.Remove(full)
|
|
}
|
|
|
|
// DeleteAll removes a file or directory recursively.
|
|
func (m *Medium) DeleteAll(p string) error {
|
|
full := m.path(p)
|
|
if len(full) < 3 {
|
|
return nil
|
|
}
|
|
return os.RemoveAll(full)
|
|
}
|
|
|
|
// Rename moves a file or directory.
|
|
func (m *Medium) Rename(oldPath, newPath string) error {
|
|
return os.Rename(m.path(oldPath), m.path(newPath))
|
|
}
|
|
|
|
// FileGet is an alias for Read.
|
|
func (m *Medium) FileGet(p string) (string, error) {
|
|
return m.Read(p)
|
|
}
|
|
|
|
// FileSet is an alias for Write.
|
|
func (m *Medium) FileSet(p, content string) error {
|
|
return m.Write(p, content)
|
|
}
|