filebrowser/backend/fileutils/file.go

168 lines
3.4 KiB
Go
Raw Permalink Normal View History

package fileutils
import (
"io"
"os"
"path"
"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 {
return nil
}
// fallback
2024-08-24 22:02:33 +00:00
err := CopyFile(src, dst)
if err != nil {
2024-08-24 22:02:33 +00:00
_ = os.Remove(dst)
return err
}
2024-08-24 22:02:33 +00:00
if err := os.Remove(src); err != nil {
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 {
// Open the source file.
2024-08-24 22:02:33 +00:00
src, err := os.Open(source)
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
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
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)
if err != nil {
return err
}
2024-08-24 22:02:33 +00:00
err = os.Chmod(dest, info.Mode())
if err != nil {
return err
}
return nil
}
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.
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.
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.
c = append(c, sep)
2024-08-24 22:02:33 +00:00
// Ignore the first path since it's already in c.
for _, v := range paths[1:] {
2024-08-24 22:02:33 +00:00
// Clean up each path before testing it.
v = path.Clean(v) + string(sep)
2024-08-24 22:02:33 +00:00
// Find the first non-common byte and truncate c.
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.
for i := len(c) - 1; i >= 0; i-- {
if c[i] == sep {
c = c[:i]
break
}
}
return string(c)
}