2019-01-05 22:44:33 +00:00
|
|
|
package fileutils
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
2020-07-15 15:12:13 +00:00
|
|
|
"os"
|
2020-09-11 14:53:37 +00:00
|
|
|
"path"
|
2019-01-05 22:44:33 +00:00
|
|
|
"path/filepath"
|
|
|
|
)
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// MoveFile moves a file from src to dst.
|
|
|
|
// By default, the rename system call is used. If src and dst point to different volumes,
|
|
|
|
// the file copy is used as a fallback.
|
|
|
|
func MoveFile(src, dst string) error {
|
|
|
|
if os.Rename(src, dst) == nil {
|
2020-12-24 16:50:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// fallback
|
2024-08-24 22:02:33 +00:00
|
|
|
err := CopyFile(src, dst)
|
2020-12-24 16:50:27 +00:00
|
|
|
if err != nil {
|
2024-08-24 22:02:33 +00:00
|
|
|
_ = os.Remove(dst)
|
2020-12-24 16:50:27 +00:00
|
|
|
return err
|
|
|
|
}
|
2024-08-24 22:02:33 +00:00
|
|
|
if err := os.Remove(src); err != nil {
|
2020-12-24 16:50:27 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-01-09 01:02:57 +00:00
|
|
|
// CopyFile copies a file or directory from source to dest and returns an error if any.
|
2024-08-24 22:02:33 +00:00
|
|
|
func CopyFile(source, dest string) error {
|
2025-01-09 01:02:57 +00:00
|
|
|
// Check if the source exists and whether it's a file or directory.
|
|
|
|
info, err := os.Stat(source)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
// If the source is a directory, copy it recursively.
|
|
|
|
return copyDirectory(source, dest)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the source is a file, copy the file.
|
|
|
|
return copySingleFile(source, dest)
|
|
|
|
}
|
|
|
|
|
|
|
|
// copySingleFile handles copying a single file.
|
|
|
|
func copySingleFile(source, dest string) error {
|
2019-01-05 22:44:33 +00:00
|
|
|
// Open the source file.
|
2024-08-24 22:02:33 +00:00
|
|
|
src, err := os.Open(source)
|
2019-01-05 22:44:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer src.Close()
|
|
|
|
|
2025-01-09 01:02:57 +00:00
|
|
|
// Create the destination directory if needed.
|
2024-08-24 22:02:33 +00:00
|
|
|
err = os.MkdirAll(filepath.Dir(dest), 0775) //nolint:gomnd
|
2019-01-05 22:44:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the destination file.
|
2024-08-24 22:02:33 +00:00
|
|
|
dst, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) //nolint:gomnd
|
2019-01-05 22:44:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer dst.Close()
|
|
|
|
|
|
|
|
// Copy the contents of the file.
|
|
|
|
_, err = io.Copy(dst, src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// Copy the mode.
|
|
|
|
info, err := os.Stat(source)
|
2019-01-05 22:44:33 +00:00
|
|
|
if err != nil {
|
2020-12-24 16:50:27 +00:00
|
|
|
return err
|
|
|
|
}
|
2024-08-24 22:02:33 +00:00
|
|
|
err = os.Chmod(dest, info.Mode())
|
2020-12-24 16:50:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-09-11 14:53:37 +00:00
|
|
|
|
2025-01-09 01:02:57 +00:00
|
|
|
// copyDirectory handles copying directories recursively.
|
|
|
|
func copyDirectory(source, dest string) error {
|
|
|
|
// Create the destination directory.
|
|
|
|
err := os.MkdirAll(dest, 0775) //nolint:gomnd
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the contents of the source directory.
|
|
|
|
entries, err := os.ReadDir(source)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate over each entry in the directory.
|
|
|
|
for _, entry := range entries {
|
|
|
|
srcPath := filepath.Join(source, entry.Name())
|
|
|
|
destPath := filepath.Join(dest, entry.Name())
|
|
|
|
|
|
|
|
if entry.IsDir() {
|
|
|
|
// Recursively copy subdirectories.
|
|
|
|
err = copyDirectory(srcPath, destPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Copy files.
|
|
|
|
err = copySingleFile(srcPath, destPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// CommonPrefix returns the common directory path of provided files.
|
2020-09-11 14:53:37 +00:00
|
|
|
func CommonPrefix(sep byte, paths ...string) string {
|
|
|
|
// Handle special cases.
|
|
|
|
switch len(paths) {
|
|
|
|
case 0:
|
|
|
|
return ""
|
|
|
|
case 1:
|
|
|
|
return path.Clean(paths[0])
|
|
|
|
}
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// Treat string as []byte, not []rune as is often done in Go.
|
2020-09-11 14:53:37 +00:00
|
|
|
c := []byte(path.Clean(paths[0]))
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// Add a trailing sep to handle the case where the common prefix directory
|
|
|
|
// is included in the path list.
|
2020-09-11 14:53:37 +00:00
|
|
|
c = append(c, sep)
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// Ignore the first path since it's already in c.
|
2020-09-11 14:53:37 +00:00
|
|
|
for _, v := range paths[1:] {
|
2024-08-24 22:02:33 +00:00
|
|
|
// Clean up each path before testing it.
|
2020-09-11 14:53:37 +00:00
|
|
|
v = path.Clean(v) + string(sep)
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// Find the first non-common byte and truncate c.
|
2020-09-11 14:53:37 +00:00
|
|
|
if len(v) < len(c) {
|
|
|
|
c = c[:len(v)]
|
|
|
|
}
|
|
|
|
for i := 0; i < len(c); i++ {
|
|
|
|
if v[i] != c[i] {
|
|
|
|
c = c[:i]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
// Remove trailing non-separator characters and the final separator.
|
2020-09-11 14:53:37 +00:00
|
|
|
for i := len(c) - 1; i >= 0; i-- {
|
|
|
|
if c[i] == sep {
|
|
|
|
c = c[:i]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(c)
|
|
|
|
}
|