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 }