/
gfile_copy.go
249 lines (238 loc) · 8.04 KB
/
gfile_copy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// 版权所有 GoFrame 作者(https://goframe.org)。保留所有权利。
//
// 本源代码形式遵循 MIT 许可协议条款。如果随此文件未分发 MIT 许可副本,
// 您可以在 https://github.com/gogf/gf 获取一份。
package 文件类
import (
"io"
"os"
"path/filepath"
"github.com/888go/goframe/errors/gcode"
"github.com/888go/goframe/errors/gerror"
)
// CopyOption 是用于 Copy* 函数的选项。
type CopyOption struct {
// 在源文件内容复制到目标文件后自动调用文件同步
Sync bool
// 保留原始文件的模式到目标文件。
// 如果为 true,则 Mode 属性将不起作用。
PreserveMode bool
// 目标文件创建时的模式
// 若PreserveMode为false,默认的文件模式为DefaultPermCopy
Mode os.FileMode
}
// 将文件/目录从`src`复制到`dst`。
//
// 如果`src`是文件,它将调用CopyFile实现复制功能,
// 否则调用CopyDir。
//
// 如果`src`是文件,但`dst`已存在且是一个文件夹,
// 则在`dst`目录下创建一个与`src`同名的文件。
//
// 示例:
// Copy("/tmp/file1", "/tmp/file2") => 将/tmp/file1复制到/tmp/file2
// Copy("/tmp/dir1", "/tmp/dir2") => 将/tmp/dir1复制到/tmp/dir2
// Copy("/tmp/file1", "/tmp/dir2") => 将/tmp/file1复制到/tmp/dir2/file1
// Copy("/tmp/dir1", "/tmp/file2") => 报错
func X复制(文件或目录路径 string, 复制到 string, 选项 ...CopyOption) error {
if 文件或目录路径 == "" {
return 错误类.X创建错误码(错误码类.CodeInvalidParameter, "source path cannot be empty")
}
if 复制到 == "" {
return 错误类.X创建错误码(错误码类.CodeInvalidParameter, "destination path cannot be empty")
}
srcStat, srcStatErr := os.Stat(文件或目录路径)
if srcStatErr != nil {
if os.IsNotExist(srcStatErr) {
return 错误类.X多层错误码并格式化(
错误码类.CodeInvalidParameter,
srcStatErr,
`the src path "%s" does not exist`,
文件或目录路径,
)
}
return 错误类.X多层错误码并格式化(
错误码类.CodeInternalError, srcStatErr, `call os.Stat on "%s" failed`, 文件或目录路径,
)
}
dstStat, dstStatErr := os.Stat(复制到)
if dstStatErr != nil && !os.IsNotExist(dstStatErr) {
return 错误类.X多层错误码并格式化(
错误码类.CodeInternalError, dstStatErr, `call os.Stat on "%s" failed`, 复制到)
}
if X是否为文件(文件或目录路径) {
var isDstExist = false
if dstStat != nil && !os.IsNotExist(dstStatErr) {
isDstExist = true
}
if isDstExist && dstStat.IsDir() {
var (
srcName = X路径取文件名(文件或目录路径)
dstPath = X路径生成(复制到, srcName)
)
return X复制文件(文件或目录路径, dstPath, 选项...)
}
return X复制文件(文件或目录路径, 复制到, 选项...)
}
if !srcStat.IsDir() && dstStat != nil && dstStat.IsDir() {
return 错误类.X创建错误码并格式化(
错误码类.CodeInvalidParameter,
`Copy failed: the src path "%s" is file, but the dst path "%s" is folder`,
文件或目录路径, 复制到,
)
}
return X复制目录(文件或目录路径, 复制到, 选项...)
}
// CopyFile 将名为 `src` 的文件内容复制到名为 `dst` 的文件中。如果目标文件不存在,将会创建该文件。如果目标文件已存在,则其所有内容将被源文件内容替换。文件模式将从源文件复制,并且复制的数据将同步/刷新到稳定的存储设备中。
// 感谢:https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
func X复制文件(路径, 复制到 string, 选项 ...CopyOption) (错误 error) {
var usedOption = getCopyOption(选项...)
if 路径 == "" {
return 错误类.X创建错误码(错误码类.CodeInvalidParameter, "source file cannot be empty")
}
if 复制到 == "" {
return 错误类.X创建错误码(错误码类.CodeInvalidParameter, "destination file cannot be empty")
}
// 如果src和dst是相同的路径,则不做任何操作。
if 路径 == 复制到 {
return nil
}
// file state check.
srcStat, srcStatErr := os.Stat(路径)
if srcStatErr != nil {
if os.IsNotExist(srcStatErr) {
return 错误类.X多层错误码并格式化(
错误码类.CodeInvalidParameter,
srcStatErr,
`the src path "%s" does not exist`,
路径,
)
}
return 错误类.X多层错误码并格式化(
错误码类.CodeInternalError, srcStatErr, `call os.Stat on "%s" failed`, 路径,
)
}
dstStat, dstStatErr := os.Stat(复制到)
if dstStatErr != nil && !os.IsNotExist(dstStatErr) {
return 错误类.X多层错误码并格式化(
错误码类.CodeInternalError, dstStatErr, `call os.Stat on "%s" failed`, 复制到,
)
}
if !srcStat.IsDir() && dstStat != nil && dstStat.IsDir() {
return 错误类.X创建错误码并格式化(
错误码类.CodeInvalidParameter,
`CopyFile failed: the src path "%s" is file, but the dst path "%s" is folder`,
路径, 复制到,
)
}
// copy file logic.
var inFile *os.File
inFile, 错误 = X打开并按只读模式(路径)
if 错误 != nil {
return
}
defer func() {
if e := inFile.Close(); e != nil {
错误 = 错误类.X多层错误并格式化(e, `file close failed for "%s"`, 路径)
}
}()
var outFile *os.File
outFile, 错误 = X创建文件与目录(复制到)
if 错误 != nil {
return
}
defer func() {
if e := outFile.Close(); e != nil {
错误 = 错误类.X多层错误并格式化(e, `file close failed for "%s"`, 复制到)
}
}()
if _, 错误 = io.Copy(outFile, inFile); 错误 != nil {
错误 = 错误类.X多层错误并格式化(错误, `io.Copy failed from "%s" to "%s"`, 路径, 复制到)
return
}
if usedOption.Sync {
if 错误 = outFile.Sync(); 错误 != nil {
错误 = 错误类.X多层错误并格式化(错误, `file sync failed for file "%s"`, 复制到)
return
}
}
if usedOption.PreserveMode {
usedOption.Mode = srcStat.Mode().Perm()
}
if 错误 = X更改权限(复制到, usedOption.Mode); 错误 != nil {
return
}
return
}
// CopyDir递归地复制一个目录树,尝试保持原有的权限设置。
//
// 注意:源目录必须存在,并且符号链接会被忽略并跳过。
func X复制目录(目录路径 string, 复制到 string, 选项 ...CopyOption) (错误 error) {
var usedOption = getCopyOption(选项...)
if 目录路径 == "" {
return 错误类.X创建错误码(错误码类.CodeInvalidParameter, "source directory cannot be empty")
}
if 复制到 == "" {
return 错误类.X创建错误码(错误码类.CodeInvalidParameter, "destination directory cannot be empty")
}
// 如果src和dst是相同的路径,则不做任何操作。
if 目录路径 == 复制到 {
return nil
}
目录路径 = filepath.Clean(目录路径)
复制到 = filepath.Clean(复制到)
si, 错误 := X取详情(目录路径)
if 错误 != nil {
return 错误
}
if !si.IsDir() {
return 错误类.X创建错误码(错误码类.CodeInvalidParameter, "source is not a directory")
}
if usedOption.PreserveMode {
usedOption.Mode = si.Mode().Perm()
}
if !X是否存在(复制到) {
if 错误 = os.MkdirAll(复制到, usedOption.Mode); 错误 != nil {
错误 = 错误类.X多层错误并格式化(
错误,
`create directory failed for path "%s", perm "%s"`,
复制到,
usedOption.Mode,
)
return
}
}
entries, 错误 := os.ReadDir(目录路径)
if 错误 != nil {
错误 = 错误类.X多层错误并格式化(错误, `read directory failed for path "%s"`, 目录路径)
return
}
for _, entry := range entries {
srcPath := filepath.Join(目录路径, entry.Name())
dstPath := filepath.Join(复制到, entry.Name())
if entry.IsDir() {
if 错误 = X复制目录(srcPath, dstPath); 错误 != nil {
return
}
} else {
// Skip symlinks.
if entry.Type()&os.ModeSymlink != 0 {
continue
}
if 错误 = X复制文件(srcPath, dstPath, 选项...); 错误 != nil {
return
}
}
}
return
}
func getCopyOption(option ...CopyOption) CopyOption {
var usedOption CopyOption
if len(option) > 0 {
usedOption = option[0]
}
if usedOption.Mode == 0 {
usedOption.Mode = DefaultPermCopy
}
return usedOption
}