Files
ddc/internal/files/move.go
T
2026-10-25 20:24:50 -04:00

102 lines
2.1 KiB
Go
Executable File

package files
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"syscall"
"ddc/internal/diskspace"
)
var ErrDestinationExists = errors.New("an item with this name already exists in the destination")
func MoveInto(source, destinationDir string, reservedBytes int64, progress func(int64)) error {
name := filepath.Base(source)
target := filepath.Join(destinationDir, name)
if exists(target) {
return fmt.Errorf("%w: %s", ErrDestinationExists, name)
}
size, err := TreeSize(source)
if err != nil {
return err
}
renameErr := os.Rename(source, target)
if renameErr == nil {
progress(size)
return nil
}
if !errors.Is(renameErr, syscall.EXDEV) {
return renameErr
}
if err := diskspace.EnsureFree(destinationDir, size, reservedBytes); err != nil {
return err
}
if err := copyTree(source, target, progress); err != nil {
os.RemoveAll(target)
return err
}
return os.RemoveAll(source)
}
func copyTree(source, target string, progress func(int64)) error {
return filepath.WalkDir(source, func(current string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
relative, err := filepath.Rel(source, current)
if err != nil {
return err
}
destination := filepath.Join(target, relative)
switch {
case entry.IsDir():
return os.MkdirAll(destination, 0o755)
case entry.Type().IsRegular():
return copyFile(current, destination, progress)
default:
return nil
}
})
}
func copyFile(source, destination string, progress func(int64)) error {
input, err := os.Open(source)
if err != nil {
return err
}
defer input.Close()
output, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
if err != nil {
return err
}
_, copyErr := io.Copy(output, &progressReader{reader: input, progress: progress})
closeErr := output.Close()
if copyErr != nil {
return copyErr
}
return closeErr
}
type progressReader struct {
reader io.Reader
progress func(int64)
}
func (reader *progressReader) Read(buffer []byte) (int, error) {
count, err := reader.reader.Read(buffer)
if count > 0 {
reader.progress(int64(count))
}
return count, err
}