github.com/gogf/gf/v2@v2.7.4/os/gfile/gfile_copy.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package gfile
     8  
     9  import (
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/gogf/gf/v2/errors/gcode"
    15  	"github.com/gogf/gf/v2/errors/gerror"
    16  )
    17  
    18  // CopyOption is the option for Copy* functions.
    19  type CopyOption struct {
    20  	// Auto call file sync after source file content copied to target file.
    21  	Sync bool
    22  
    23  	// Preserve the mode of the original file to the target file.
    24  	// If true, the Mode attribute will make no sense.
    25  	PreserveMode bool
    26  
    27  	// Destination created file mode.
    28  	// The default file mode is DefaultPermCopy if PreserveMode is false.
    29  	Mode os.FileMode
    30  }
    31  
    32  // Copy file/directory from `src` to `dst`.
    33  //
    34  // If `src` is file, it calls CopyFile to implements copy feature,
    35  // or else it calls CopyDir.
    36  //
    37  // If `src` is file, but `dst` already exists and is a folder,
    38  // it then creates a same name file of `src` in folder `dst`.
    39  //
    40  // Eg:
    41  // Copy("/tmp/file1", "/tmp/file2") => /tmp/file1 copied to /tmp/file2
    42  // Copy("/tmp/dir1",  "/tmp/dir2")  => /tmp/dir1  copied to /tmp/dir2
    43  // Copy("/tmp/file1", "/tmp/dir2")  => /tmp/file1 copied to /tmp/dir2/file1
    44  // Copy("/tmp/dir1",  "/tmp/file2") => error
    45  func Copy(src string, dst string, option ...CopyOption) error {
    46  	if src == "" {
    47  		return gerror.NewCode(gcode.CodeInvalidParameter, "source path cannot be empty")
    48  	}
    49  	if dst == "" {
    50  		return gerror.NewCode(gcode.CodeInvalidParameter, "destination path cannot be empty")
    51  	}
    52  	srcStat, srcStatErr := os.Stat(src)
    53  	if srcStatErr != nil {
    54  		if os.IsNotExist(srcStatErr) {
    55  			return gerror.WrapCodef(
    56  				gcode.CodeInvalidParameter,
    57  				srcStatErr,
    58  				`the src path "%s" does not exist`,
    59  				src,
    60  			)
    61  		}
    62  		return gerror.WrapCodef(
    63  			gcode.CodeInternalError, srcStatErr, `call os.Stat on "%s" failed`, src,
    64  		)
    65  	}
    66  	dstStat, dstStatErr := os.Stat(dst)
    67  	if dstStatErr != nil && !os.IsNotExist(dstStatErr) {
    68  		return gerror.WrapCodef(
    69  			gcode.CodeInternalError, dstStatErr, `call os.Stat on "%s" failed`, dst)
    70  	}
    71  
    72  	if IsFile(src) {
    73  		var isDstExist = false
    74  		if dstStat != nil && !os.IsNotExist(dstStatErr) {
    75  			isDstExist = true
    76  		}
    77  		if isDstExist && dstStat.IsDir() {
    78  			var (
    79  				srcName = Basename(src)
    80  				dstPath = Join(dst, srcName)
    81  			)
    82  			return CopyFile(src, dstPath, option...)
    83  		}
    84  		return CopyFile(src, dst, option...)
    85  	}
    86  	if !srcStat.IsDir() && dstStat != nil && dstStat.IsDir() {
    87  		return gerror.NewCodef(
    88  			gcode.CodeInvalidParameter,
    89  			`Copy failed: the src path "%s" is file, but the dst path "%s" is folder`,
    90  			src, dst,
    91  		)
    92  	}
    93  	return CopyDir(src, dst, option...)
    94  }
    95  
    96  // CopyFile copies the contents of the file named `src` to the file named
    97  // by `dst`. The file will be created if it does not exist. If the
    98  // destination file exists, all it's contents will be replaced by the contents
    99  // of the source file. The file mode will be copied from the source and
   100  // the copied data is synced/flushed to stable storage.
   101  // Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
   102  func CopyFile(src, dst string, option ...CopyOption) (err error) {
   103  	var usedOption = getCopyOption(option...)
   104  	if src == "" {
   105  		return gerror.NewCode(gcode.CodeInvalidParameter, "source file cannot be empty")
   106  	}
   107  	if dst == "" {
   108  		return gerror.NewCode(gcode.CodeInvalidParameter, "destination file cannot be empty")
   109  	}
   110  	// If src and dst are the same path, it does nothing.
   111  	if src == dst {
   112  		return nil
   113  	}
   114  	// file state check.
   115  	srcStat, srcStatErr := os.Stat(src)
   116  	if srcStatErr != nil {
   117  		if os.IsNotExist(srcStatErr) {
   118  			return gerror.WrapCodef(
   119  				gcode.CodeInvalidParameter,
   120  				srcStatErr,
   121  				`the src path "%s" does not exist`,
   122  				src,
   123  			)
   124  		}
   125  		return gerror.WrapCodef(
   126  			gcode.CodeInternalError, srcStatErr, `call os.Stat on "%s" failed`, src,
   127  		)
   128  	}
   129  	dstStat, dstStatErr := os.Stat(dst)
   130  	if dstStatErr != nil && !os.IsNotExist(dstStatErr) {
   131  		return gerror.WrapCodef(
   132  			gcode.CodeInternalError, dstStatErr, `call os.Stat on "%s" failed`, dst,
   133  		)
   134  	}
   135  	if !srcStat.IsDir() && dstStat != nil && dstStat.IsDir() {
   136  		return gerror.NewCodef(
   137  			gcode.CodeInvalidParameter,
   138  			`CopyFile failed: the src path "%s" is file, but the dst path "%s" is folder`,
   139  			src, dst,
   140  		)
   141  	}
   142  	// copy file logic.
   143  	var inFile *os.File
   144  	inFile, err = Open(src)
   145  	if err != nil {
   146  		return
   147  	}
   148  	defer func() {
   149  		if e := inFile.Close(); e != nil {
   150  			err = gerror.Wrapf(e, `file close failed for "%s"`, src)
   151  		}
   152  	}()
   153  	var outFile *os.File
   154  	outFile, err = Create(dst)
   155  	if err != nil {
   156  		return
   157  	}
   158  	defer func() {
   159  		if e := outFile.Close(); e != nil {
   160  			err = gerror.Wrapf(e, `file close failed for "%s"`, dst)
   161  		}
   162  	}()
   163  	if _, err = io.Copy(outFile, inFile); err != nil {
   164  		err = gerror.Wrapf(err, `io.Copy failed from "%s" to "%s"`, src, dst)
   165  		return
   166  	}
   167  	if usedOption.Sync {
   168  		if err = outFile.Sync(); err != nil {
   169  			err = gerror.Wrapf(err, `file sync failed for file "%s"`, dst)
   170  			return
   171  		}
   172  	}
   173  	if usedOption.PreserveMode {
   174  		usedOption.Mode = srcStat.Mode().Perm()
   175  	}
   176  	if err = Chmod(dst, usedOption.Mode); err != nil {
   177  		return
   178  	}
   179  	return
   180  }
   181  
   182  // CopyDir recursively copies a directory tree, attempting to preserve permissions.
   183  //
   184  // Note that, the Source directory must exist and symlinks are ignored and skipped.
   185  func CopyDir(src string, dst string, option ...CopyOption) (err error) {
   186  	var usedOption = getCopyOption(option...)
   187  	if src == "" {
   188  		return gerror.NewCode(gcode.CodeInvalidParameter, "source directory cannot be empty")
   189  	}
   190  	if dst == "" {
   191  		return gerror.NewCode(gcode.CodeInvalidParameter, "destination directory cannot be empty")
   192  	}
   193  	// If src and dst are the same path, it does nothing.
   194  	if src == dst {
   195  		return nil
   196  	}
   197  	src = filepath.Clean(src)
   198  	dst = filepath.Clean(dst)
   199  	si, err := Stat(src)
   200  	if err != nil {
   201  		return err
   202  	}
   203  	if !si.IsDir() {
   204  		return gerror.NewCode(gcode.CodeInvalidParameter, "source is not a directory")
   205  	}
   206  	if usedOption.PreserveMode {
   207  		usedOption.Mode = si.Mode().Perm()
   208  	}
   209  	if !Exists(dst) {
   210  		if err = os.MkdirAll(dst, usedOption.Mode); err != nil {
   211  			err = gerror.Wrapf(
   212  				err,
   213  				`create directory failed for path "%s", perm "%s"`,
   214  				dst,
   215  				usedOption.Mode,
   216  			)
   217  			return
   218  		}
   219  	}
   220  	entries, err := os.ReadDir(src)
   221  	if err != nil {
   222  		err = gerror.Wrapf(err, `read directory failed for path "%s"`, src)
   223  		return
   224  	}
   225  	for _, entry := range entries {
   226  		srcPath := filepath.Join(src, entry.Name())
   227  		dstPath := filepath.Join(dst, entry.Name())
   228  		if entry.IsDir() {
   229  			if err = CopyDir(srcPath, dstPath); err != nil {
   230  				return
   231  			}
   232  		} else {
   233  			// Skip symlinks.
   234  			if entry.Type()&os.ModeSymlink != 0 {
   235  				continue
   236  			}
   237  			if err = CopyFile(srcPath, dstPath, option...); err != nil {
   238  				return
   239  			}
   240  		}
   241  	}
   242  	return
   243  }
   244  
   245  func getCopyOption(option ...CopyOption) CopyOption {
   246  	var usedOption CopyOption
   247  	if len(option) > 0 {
   248  		usedOption = option[0]
   249  	}
   250  	if usedOption.Mode == 0 {
   251  		usedOption.Mode = DefaultPermCopy
   252  	}
   253  	return usedOption
   254  }