102 lines
2.1 KiB
Go
Executable File
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
|
|
}
|