199 lines
4.4 KiB
Go
199 lines
4.4 KiB
Go
package fileutils
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
|
|
"github.com/mholt/archiver/v3"
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/filebrowser/filebrowser/v2/files"
|
|
)
|
|
|
|
// MoveFile moves file from src to dst.
|
|
// By default the rename filesystem system call is used. If src and dst point to different volumes
|
|
// the file copy is used as a fallback
|
|
func MoveFile(fs afero.Fs, src, dst string) error {
|
|
if fs.Rename(src, dst) == nil {
|
|
return nil
|
|
}
|
|
|
|
if info, err := fs.Stat(src); err == nil && info.IsDir() {
|
|
return CopyFolder(fs, src, dst)
|
|
}
|
|
|
|
// fallback
|
|
err := Copy(fs, src, dst)
|
|
if err != nil {
|
|
_ = fs.Remove(dst)
|
|
return err
|
|
}
|
|
if err := fs.RemoveAll(src); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CopyFile copies a file from source to dest and returns
|
|
// an error if any.
|
|
func CopyFile(fs afero.Fs, source, dest string) error {
|
|
// Open the source file.
|
|
src, err := fs.Open(source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer src.Close()
|
|
|
|
// Makes the directory needed to create the dst
|
|
// file.
|
|
err = fs.MkdirAll(filepath.Dir(dest), files.PermDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the destination file.
|
|
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, files.PermFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dst.Close()
|
|
|
|
// Copy the contents of the file.
|
|
_, err = io.Copy(dst, src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy the mode
|
|
info, err := fs.Stat(source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = fs.Chmod(dest, info.Mode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyFolder copies a folder to destination and returns
|
|
// an error if any. If the destination already exists,
|
|
// it gets overridden.
|
|
func CopyFolder(fs afero.Fs, source, dest string) error {
|
|
tmpDest := ""
|
|
_, err := fs.Stat(dest)
|
|
if err == nil {
|
|
// destination already exists - rename it so that src
|
|
// could be moved to its place
|
|
maxRn := 9999
|
|
rn, errrn := rand.Int(rand.Reader, big.NewInt(int64(maxRn)))
|
|
if errrn != nil {
|
|
return errrn
|
|
}
|
|
|
|
tmpDest = fmt.Sprintf("%s.%d", dest, rn.Int64())
|
|
|
|
err = fs.Rename(dest, tmpDest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if !os.IsNotExist(err) {
|
|
// unknown error, return
|
|
return err
|
|
}
|
|
|
|
if err = fs.Rename(source, dest); err != nil {
|
|
// if moving fails and we renamed an existing destination
|
|
// in the previous step, then restore its name
|
|
if tmpDest != "" {
|
|
err = fs.Rename(tmpDest, dest)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// if moving was successful, delete the renamed dest
|
|
_ = fs.Remove(tmpDest)
|
|
|
|
return nil
|
|
}
|
|
|
|
// CommonPrefix returns 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])
|
|
}
|
|
|
|
// Note, we treat string as []byte, not []rune as is often
|
|
// done in Go. (And sep as byte, not rune). This is because
|
|
// most/all supported OS' treat paths as string of non-zero
|
|
// bytes. A filename may be displayed as a sequence of Unicode
|
|
// runes (typically encoded as UTF-8) but paths are
|
|
// not required to be valid UTF-8 or in any normalized form
|
|
// (e.g. "é" (U+00C9) and "é" (U+0065,U+0301) are different
|
|
// file names.
|
|
c := []byte(path.Clean(paths[0]))
|
|
|
|
// We add a trailing sep to handle the case where the
|
|
// common prefix directory is included in the path list
|
|
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
|
|
// path.Clean will have cleaned off trailing / separators with
|
|
// the exception of the root directory, "/" (in which case we
|
|
// make it "//", but this will get fixed up to "/" below).
|
|
c = append(c, sep)
|
|
|
|
// Ignore the first path since it's already in c
|
|
for _, v := range paths[1:] {
|
|
// Clean up each path before testing it
|
|
v = path.Clean(v) + string(sep)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
func Decompress(src, dst string, decompressor archiver.Decompressor) error {
|
|
reader, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dstfd, err := os.OpenFile(dst, os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0644) //nolint:gomnd
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := decompressor.Decompress(reader, dstfd); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|