-
Notifications
You must be signed in to change notification settings - Fork 13
/
sftp.go
448 lines (390 loc) · 11 KB
/
sftp.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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
package ssh
import (
"bytes"
"errors"
"io"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
)
// Upload 上传本地文件 local 到sftp远程目录 remote like rsync
// rsync -av src/ dst ./src/* --> /root/dst/*
// rsync -av src/ dst/ ./src/* --> /root/dst/*
// rsync -av src dst ./src/* --> /root/dst/src/*
// rsync -av src dst/ ./src/* --> /root/dst/src/*
func (c *Client) Upload(local string, remote string) (err error) {
// var localDir, localFile, remoteDir, remoteFile string
info, err := os.Stat(local)
if err != nil {
return errors.New("sftp: 跳过上传 Upload(\"" + local + "\") ,本地文件不存在或格式错误!")
}
if info.IsDir() {
log.Println("sftp: UploadDir", local)
return c.UploadDir(local, remote)
}
return c.UploadFile(local, remote)
}
// Download 下载sftp远程文件 remote 到本地 local like rsync
func (c *Client) Download(remote string, local string) (err error) {
if c.IsNotExist(strings.TrimSuffix(remote, "/")) {
return errors.New("sftp: 远程文件不存在,跳过文件下载 \"" + remote + "\" ")
}
if c.IsDir(remote) {
// return errors.New("检测到远程是文件不是目录 \"" + remote + "\" 跳过下载")
return c.downloadDir(remote, local)
}
return c.downloadFile(remote, local)
}
// downloadFile a file from the remote server like cp
func (c *Client) downloadFile(remoteFile, local string) error {
// remoteFile = strings.TrimSuffix(remoteFile, "/")
if !c.IsFile(remoteFile) {
return errors.New("sftp: 文件不存在或不是文件, 跳过目录下载 downloadFile(" + remoteFile + ")")
}
var localFile string
if local[len(local)-1] == '/' {
localFile = filepath.Join(local, filepath.Base(remoteFile))
} else {
localFile = local
}
localFile = filepath.ToSlash(localFile)
if c.Size(remoteFile) > 1000 {
rsum := c.Md5File(remoteFile)
ioutil.WriteFile(localFile+".md5", []byte(rsum), 755)
if FileExist(localFile) {
if rsum != "" {
lsum, _ := Md5File(localFile)
if lsum == rsum {
log.Println("sftp: 文件与本地一致,跳过下载!", localFile)
return nil
}
log.Println("sftp: 正在下载 ", localFile)
}
}
}
if err := os.MkdirAll(filepath.Dir(localFile), os.ModePerm); err != nil {
// log.Println(err)
return err
}
r, err := c.SFTPClient.Open(remoteFile)
if err != nil {
return err
}
defer r.Close()
l, err := os.Create(localFile)
if err != nil {
return err
}
defer l.Close()
_, err = io.Copy(l, r)
return err
}
// downloadDir from remote dir to local dir like rsync
// rsync -av src/ dst ./src/* --> /root/dst/*
// rsync -av src/ dst/ ./src/* --> /root/dst/*
// rsync -av src dst ./src/* --> /root/dst/src/*
// rsync -av src dst/ ./src/* --> /root/dst/src/*
func (c *Client) downloadDir(remote, local string) error {
var localDir, remoteDir string
if !c.IsDir(remote) {
return errors.New("sftp: 目录不存在或不是目录, 跳过 downloadDir(" + remote + ")")
}
remoteDir = remote
if remote[len(remote)-1] == '/' {
localDir = local
} else {
localDir = path.Join(local, path.Base(remote))
}
walker := c.SFTPClient.Walk(remoteDir)
for walker.Step() {
if err := walker.Err(); err != nil {
log.Println(err)
continue
}
info := walker.Stat()
relPath, err := filepath.Rel(remoteDir, walker.Path())
if err != nil {
return err
}
localPath := filepath.ToSlash(filepath.Join(localDir, relPath))
// if we have something at the download path delete it if it is a directory
// and the remote is a file and vice a versa
localInfo, err := os.Stat(localPath)
if os.IsExist(err) {
if localInfo.IsDir() {
if info.IsDir() {
continue
}
err = os.RemoveAll(localPath)
if err != nil {
return err
}
} else if info.IsDir() {
err = os.Remove(localPath)
if err != nil {
return err
}
}
}
if info.IsDir() {
err = os.MkdirAll(localPath, os.ModePerm)
if err != nil {
return err
}
continue
}
c.downloadFile(walker.Path(), localPath)
}
return nil
}
//UploadFile 上传本地文件 localFile 到sftp远程目录 remote
func (c *Client) UploadFile(localFile, remote string) error {
// localFile = strings.TrimSuffix(localFile, "/")
// localFile = filepath.ToSlash(localFile)
info, err := os.Stat(localFile)
if err != nil || info.IsDir() {
return errors.New("sftp: 本地文件不存在,或是不是文件 UploadFile(\"" + localFile + "\") 跳过上传")
}
l, err := os.Open(localFile)
if err != nil {
return err
}
defer l.Close()
var remoteFile, remoteDir string
if remote[len(remote)-1] == '/' {
remoteFile = filepath.ToSlash(filepath.Join(remote, filepath.Base(localFile)))
remoteDir = remote
} else {
remoteFile = remote
remoteDir = filepath.ToSlash(filepath.Dir(remoteFile))
}
log.Println("sftp: UploadFile", localFile, remoteFile)
if info.Size() > 1000 {
// 1. 检测远程是否存在
rsum := c.Md5File(remoteFile)
if rsum != "" {
lsum, _ := Md5File(localFile)
if lsum == rsum {
log.Println("sftp: 文件与本地一致,跳过上传!", localFile)
return nil
}
log.Println("sftp: 正在上传 ", localFile)
}
}
// 目录不存在,则创建 remoteDir
if _, err := c.SFTPClient.Stat(remoteDir); err != nil {
log.Println("sftp: Mkdir all", remoteDir)
c.MkdirAll(remoteDir)
}
r, err := c.SFTPClient.Create(remoteFile)
if err != nil {
return err
}
_, err = io.Copy(r, l)
return err
}
// UploadDir files without checking diff status
func (c *Client) UploadDir(localDir string, remoteDir string) (err error) {
// defer func() {
// if err != nil {
// err = errors.New("UploadDir " + err.Error())
// }
// }()
// 本地输入检测,必须是目录
// localDir = filepath.ToSlash(localDir)
info, err := os.Stat(localDir)
if err != nil || !info.IsDir() {
return errors.New("sftp: 本地目录不存在或不是目录 UploadDir(\"" + localDir + "\") 跳过上传")
}
// 模仿 rsync localDir不以'/'结尾,则创建尾目录
if localDir[len(localDir)-1] != '/' {
remoteDir = filepath.ToSlash(filepath.Join(remoteDir, filepath.Base(localDir)))
}
log.Println("sftp: UploadDir", localDir, remoteDir)
rootDst := strings.TrimSuffix(remoteDir, "/")
if c.IsFile(rootDst) {
c.SFTPClient.Remove(rootDst)
}
walkFunc := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Calculate the final destination using the
// base source and root destination
relSrc, err := filepath.Rel(localDir, path)
if err != nil {
return err
}
finalDst := filepath.Join(rootDst, relSrc)
// In Windows, Join uses backslashes which we don't want to get
// to the sftp server
finalDst = filepath.ToSlash(finalDst)
// Skip the creation of the target destination directory since
// it should exist and we might not even own it
if finalDst == remoteDir {
return nil
log.Println("sftp: ", remoteDir, "--->", finalDst)
}
if info.IsDir() {
err := c.MkdirAll(finalDst)
if err != nil {
log.Println("sftp: MkdirAll", err)
}
// log.Println("MkdirAll", finalDst)
// err = c.SFTPClient.Mkdir(finalDst)
// log.Println(err)
// if err := c.SFTPClient.Mkdir(finalDst); err != nil {
// // Do not consider it an error if the directory existed
// remoteFi, fiErr := c.SFTPClient.Lstat(finalDst)
// if fiErr != nil || !remoteFi.IsDir() {
// return err
// }
// }
// return err
} else {
// f, err := os.Open(path)
// if err != nil {
// return err
// }
// defer f.Close()
return c.UploadFile(path, finalDst)
}
return nil
}
return filepath.Walk(localDir, walkFunc)
}
// Remove a file from the remote server
func (c *Client) Remove(path string) error {
return c.SFTPClient.Remove(path)
}
// RemoveDirectory Remove a directory from the remote server
func (c *Client) RemoveDirectory(path string) error {
return c.SFTPClient.RemoveDirectory(path)
}
// ReadAll Read a remote file and return the contents.
func (c *Client) ReadAll(filepath string) ([]byte, error) {
file, err := c.SFTPClient.Open(filepath)
if err != nil {
return nil, err
}
defer file.Close()
return ioutil.ReadAll(file)
}
//FileExist 文件是否存在
func (c *Client) FileExist(filepath string) (bool, error) {
if _, err := c.SFTPClient.Stat(filepath); err != nil {
return false, err
}
return true, nil
}
func (c *Client) RemoveFile(remoteFile string) error {
return c.SFTPClient.Remove(remoteFile)
}
func (c *Client) RemoveDir(remoteDir string) error {
remoteFiles, err := c.SFTPClient.ReadDir(remoteDir)
if err != nil {
log.Printf("sftp: remove remote dir: %s err: %v\n", remoteDir, err)
return err
}
for _, file := range remoteFiles {
subRemovePath := path.Join(remoteDir, file.Name())
if file.IsDir() {
c.RemoveDir(subRemovePath)
} else {
c.RemoveFile(subRemovePath)
}
}
c.SFTPClient.RemoveDirectory(remoteDir) //must empty dir to remove
log.Printf("sftp: remove remote dir: %s ok\n", remoteDir)
return nil
}
//RemoveAll 递归删除目录,文件
func (c *Client) RemoveAll(remoteDir string) error {
c.RemoveDir(remoteDir)
return nil
}
//MkdirAll 创建目录,递归
func (c *Client) MkdirAll(dirpath string) error {
parentDir := filepath.ToSlash(filepath.Dir(dirpath))
_, err := c.SFTPClient.Stat(parentDir)
if err != nil {
// log.Println(err)
if err.Error() == "file does not exist" {
err := c.MkdirAll(parentDir)
if err != nil {
return err
}
} else {
return err
}
}
err = c.SFTPClient.Mkdir(filepath.ToSlash(dirpath))
if err != nil {
return err
}
return nil
}
func (c *Client) Mkdir(path string, fi os.FileInfo) error {
log.Printf("[DEBUG] sftp: creating dir %s", path)
if err := c.SFTPClient.Mkdir(path); err != nil {
// Do not consider it an error if the directory existed
remoteFi, fiErr := c.SFTPClient.Lstat(path)
if fiErr != nil || !remoteFi.IsDir() {
return err
}
}
mode := fi.Mode().Perm()
if err := c.SFTPClient.Chmod(path, mode); err != nil {
return err
}
return nil
}
//IsDir 检查远程是否是个目录
func (c *Client) IsDir(path string) bool {
// 检查远程是文件还是目录
info, err := c.SFTPClient.Stat(path)
if err == nil && info.IsDir() {
return true
}
return false
}
//Size 获取文件大小
func (c *Client) Size(path string) int64 {
info, err := c.SFTPClient.Stat(path)
if err != nil {
return 0
}
return info.Size()
}
//IsFile 检查远程是否是个文件
func (c *Client) IsFile(path string) bool {
info, err := c.SFTPClient.Stat(path)
if err == nil && !info.IsDir() {
return true
}
return false
}
//IsNotExist 检查远程是文件是否不存在
func (c *Client) IsNotExist(path string) bool {
_, err := c.SFTPClient.Stat(path)
return err != nil
}
//IsExist 检查远程是文件是否存在
func (c *Client) IsExist(path string) bool {
_, err := c.SFTPClient.Stat(path)
return err == nil
}
//Md5File 检查远程是文件是否存在
func (c *Client) Md5File(path string) string {
if c.IsNotExist(path) {
return ""
}
b, err := c.Output("md5sum " + path)
if err != nil {
return ""
}
return string(bytes.Split(b, []byte{' '})[0])
}