FastViewer / backend /internal /services /filesystem.go
StarrySkyWorld's picture
Init
8820bb8
package services
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
type FSService struct {
root string
}
type Entry struct {
Name string `json:"name"`
Path string `json:"path"`
Size int64 `json:"size"`
IsDir bool `json:"isDir"`
ModTime time.Time `json:"modTime"`
}
func NewFSService(root string) *FSService {
return &FSService{root: filepath.Clean(root)}
}
func (s *FSService) ResolvePath(raw string) (string, error) {
clean := filepath.Clean(filepath.Join(s.root, raw))
rootWithSep := s.root + string(os.PathSeparator)
if clean != s.root && !strings.HasPrefix(clean, rootWithSep) {
return "", errors.New("path out of root directory")
}
return clean, nil
}
func (s *FSService) List(raw string) ([]Entry, error) {
path, err := s.ResolvePath(raw)
if err != nil {
return nil, err
}
entries, err := os.ReadDir(path)
if err != nil {
return nil, fmt.Errorf("read dir: %w", err)
}
result := make([]Entry, 0, len(entries))
for _, e := range entries {
info, err := e.Info()
if err != nil {
continue
}
full := filepath.Join(path, e.Name())
rel, _ := filepath.Rel(s.root, full)
result = append(result, Entry{
Name: e.Name(),
Path: filepath.ToSlash(rel),
Size: info.Size(),
IsDir: e.IsDir(),
ModTime: info.ModTime(),
})
}
sort.Slice(result, func(i, j int) bool {
if result[i].IsDir != result[j].IsDir {
return result[i].IsDir
}
return strings.ToLower(result[i].Name) < strings.ToLower(result[j].Name)
})
return result, nil
}
func (s *FSService) Delete(raw string) error {
path, err := s.ResolvePath(raw)
if err != nil {
return err
}
return os.RemoveAll(path)
}
func (s *FSService) Rename(rawPath, newName string) error {
if strings.Contains(newName, "/") || strings.Contains(newName, "\\") {
return errors.New("invalid name")
}
path, err := s.ResolvePath(rawPath)
if err != nil {
return err
}
target := filepath.Join(filepath.Dir(path), newName)
target, err = s.ResolvePath(mustRel(s.root, target))
if err != nil {
return err
}
return os.Rename(path, target)
}
func (s *FSService) Move(fromRaw, toRaw string) error {
fromPath, err := s.ResolvePath(fromRaw)
if err != nil {
return err
}
toPath, err := s.ResolvePath(toRaw)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(toPath), 0o755); err != nil {
return err
}
return os.Rename(fromPath, toPath)
}
func (s *FSService) SaveUploadedFile(src io.Reader, targetRaw string) error {
targetPath, err := s.ResolvePath(targetRaw)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
return err
}
dst, err := os.Create(targetPath)
if err != nil {
return err
}
defer dst.Close()
_, err = io.Copy(dst, src)
return err
}
func mustRel(root, target string) string {
rel, err := filepath.Rel(root, target)
if err != nil {
return "."
}
return rel
}