gfile_copy.go 6.9 KB

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