package fileutils import ( "crypto/rand" "fmt" "io" "math/big" "os" "path" "path/filepath" "github.com/mholt/archiver/v3" "github.com/spf13/afero" ) // 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 if err := CopyFile(fs, src, dst); 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), 0666) //nolint:gomnd if err != nil { return err } // Create the destination file. dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) //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 } // 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 "/" bellow). 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 }