github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/bisync/bilib/files.go (about)

     1  // Package bilib provides common stuff for bisync and bisync_test
     2  // Here it's got local file/directory helpers (nice to have in lib/file)
     3  package bilib
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  )
    13  
    14  // PermSecure is a Unix permission for a file accessible only by its owner
    15  const PermSecure = 0600
    16  
    17  var (
    18  	regexLocalPath   = regexp.MustCompile(`^[./\\]`)
    19  	regexWindowsPath = regexp.MustCompile(`^[a-zA-Z]:`)
    20  	regexRemotePath  = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*:`)
    21  )
    22  
    23  // IsLocalPath returns true if its argument is a non-remote path.
    24  // Empty string or a relative path will be considered local.
    25  // Note: `c:dir` will be considered local on Windows but remote on Linux.
    26  func IsLocalPath(path string) bool {
    27  	if path == "" || regexLocalPath.MatchString(path) {
    28  		return true
    29  	}
    30  	if runtime.GOOS == "windows" && regexWindowsPath.MatchString(path) {
    31  		return true
    32  	}
    33  	return !regexRemotePath.MatchString(path)
    34  }
    35  
    36  // FileExists returns true if the local file exists
    37  func FileExists(file string) bool {
    38  	_, err := os.Stat(file)
    39  	return !os.IsNotExist(err)
    40  }
    41  
    42  // CopyFileIfExists is like CopyFile but does not fail if source does not exist
    43  func CopyFileIfExists(srcFile, dstFile string) error {
    44  	if !FileExists(srcFile) {
    45  		return nil
    46  	}
    47  	return CopyFile(srcFile, dstFile)
    48  }
    49  
    50  // CopyFile copies a local file
    51  func CopyFile(src, dst string) (err error) {
    52  	var (
    53  		rd   io.ReadCloser
    54  		wr   io.WriteCloser
    55  		info os.FileInfo
    56  	)
    57  	if info, err = os.Stat(src); err != nil {
    58  		return
    59  	}
    60  	if rd, err = os.Open(src); err != nil {
    61  		return
    62  	}
    63  	defer func() {
    64  		_ = rd.Close()
    65  	}()
    66  	if wr, err = os.Create(dst); err != nil {
    67  		return
    68  	}
    69  	_, err = io.Copy(wr, rd)
    70  	if e := wr.Close(); err == nil {
    71  		err = e
    72  	}
    73  	if e := os.Chmod(dst, info.Mode()); err == nil {
    74  		err = e
    75  	}
    76  	if e := os.Chtimes(dst, info.ModTime(), info.ModTime()); err == nil {
    77  		err = e
    78  	}
    79  	return
    80  }
    81  
    82  // CopyDir copies a local directory
    83  func CopyDir(src string, dst string) (err error) {
    84  	src = filepath.Clean(src)
    85  	dst = filepath.Clean(dst)
    86  
    87  	si, err := os.Stat(src)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	if !si.IsDir() {
    92  		return fmt.Errorf("source is not a directory")
    93  	}
    94  
    95  	_, err = os.Stat(dst)
    96  	if err != nil && !os.IsNotExist(err) {
    97  		return
    98  	}
    99  	if err == nil {
   100  		return fmt.Errorf("destination already exists")
   101  	}
   102  
   103  	err = os.MkdirAll(dst, si.Mode())
   104  	if err != nil {
   105  		return
   106  	}
   107  
   108  	entries, err := os.ReadDir(src)
   109  	if err != nil {
   110  		return
   111  	}
   112  
   113  	for _, entry := range entries {
   114  		srcPath := filepath.Join(src, entry.Name())
   115  		dstPath := filepath.Join(dst, entry.Name())
   116  
   117  		if entry.IsDir() {
   118  			err = CopyDir(srcPath, dstPath)
   119  			if err != nil {
   120  				return
   121  			}
   122  		} else {
   123  			// Skip symlinks.
   124  			if entry.Type()&os.ModeSymlink != 0 {
   125  				continue
   126  			}
   127  
   128  			err = CopyFile(srcPath, dstPath)
   129  			if err != nil {
   130  				return
   131  			}
   132  		}
   133  	}
   134  
   135  	return
   136  }