ftpclient.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. // Author: NiuJiuRu
  2. // Email: niujiuru@qq.com
  3. package ftpclient
  4. import (
  5. "context"
  6. "fmt"
  7. "io"
  8. "net"
  9. "os"
  10. "path/filepath"
  11. "sync"
  12. "sync/atomic"
  13. "time"
  14. "github.com/jlaffaye/ftp"
  15. "hnyfkj.com.cn/rtu/linux/baseapp"
  16. )
  17. const MODULE_NAME = "FtpClient"
  18. const (
  19. defaultRtyInterval = 1 * time.Second
  20. defaultLogInterval = 2 * time.Second
  21. DefaultUploadTimeout = 5 * time.Minute
  22. DefaultDownloadTimeout = 5 * time.Minute
  23. useFTPConnDeadline = true
  24. )
  25. var (
  26. fileLock = struct {
  27. sync.Mutex
  28. m map[string]struct{}
  29. }{m: make(map[string]struct{})}
  30. FileUpFolder = "" // 上传文件目录
  31. FileUploader = &uploader{} // 拍照上传任务只能串行进行时, 可以通过使用该锁来实现排队串行
  32. )
  33. type uploader struct {
  34. uploadLock sync.Mutex
  35. }
  36. func (u *uploader) Lock() {
  37. u.uploadLock.Lock()
  38. }
  39. func (u *uploader) Unlock() {
  40. u.uploadLock.Unlock()
  41. }
  42. func tryLockFile(file string) (unlock func(), ok bool) {
  43. fileLock.Lock()
  44. defer fileLock.Unlock()
  45. if _, ok := fileLock.m[file]; ok {
  46. return nil, false
  47. }
  48. fileLock.m[file] = struct{}{}
  49. return func() {
  50. fileLock.Lock()
  51. delete(fileLock.m, file)
  52. fileLock.Unlock()
  53. }, true
  54. }
  55. type progressReader struct {
  56. io.Reader
  57. filePath string
  58. total, transferred int64
  59. label string // "上传"或"下载"
  60. ctx context.Context
  61. doneLogged int32
  62. }
  63. func newProgressReader(r io.Reader, filePath string, total, transferred int64, label string, ctx context.Context) *progressReader {
  64. pr := &progressReader{Reader: r, filePath: filePath, total: total, transferred: transferred, label: label, ctx: ctx}
  65. go pr.startProgressLogger()
  66. return pr
  67. }
  68. func (p *progressReader) Read(buf []byte) (int, error) {
  69. n, err := p.Reader.Read(buf)
  70. if n > 0 {
  71. atomic.AddInt64(&p.transferred, int64(n))
  72. }
  73. if err == io.EOF && atomic.CompareAndSwapInt32(&p.doneLogged, 0, 1) {
  74. transferred := atomic.LoadInt64(&p.transferred)
  75. baseapp.Logger.Infof("[%s] 文件%q%s进度: 100.00%%, 剩余: %d字节, 总大小: %d字节", MODULE_NAME, p.filePath, p.label, p.total-transferred, p.total)
  76. }
  77. return n, err
  78. }
  79. func (p *progressReader) startProgressLogger() {
  80. ticker := time.NewTicker(defaultLogInterval)
  81. defer ticker.Stop()
  82. for {
  83. select {
  84. case <-ticker.C:
  85. if atomic.LoadInt32(&p.doneLogged) == 1 {
  86. return
  87. }
  88. transferred := atomic.LoadInt64(&p.transferred)
  89. if transferred >= p.total {
  90. return
  91. }
  92. progress := float64(transferred) / float64(p.total) * 100
  93. baseapp.Logger.Infof("[%s] 文件%q%s进度: %.2f%%, 剩余: %d字节, 总大小: %d字节", MODULE_NAME, p.filePath, p.label, progress, p.total-transferred, p.total)
  94. case <-p.ctx.Done():
  95. return
  96. }
  97. }
  98. }
  99. type stopError struct{ err error }
  100. func (e *stopError) Error() string { return e.err.Error() }
  101. func dialFTP(ctx context.Context, serverAddr string) (*ftp.ServerConn, error) {
  102. if !useFTPConnDeadline {
  103. return ftp.Dial(serverAddr, ftp.DialWithContext(ctx))
  104. }
  105. return ftp.Dial(serverAddr, ftp.DialWithDialFunc(func(network, address string) (net.Conn, error) {
  106. conn, err := (&net.Dialer{}).DialContext(ctx, network, address)
  107. if err != nil {
  108. return nil, err
  109. }
  110. if deadline, ok := ctx.Deadline(); ok {
  111. if err := conn.SetDeadline(deadline); err != nil {
  112. _ = conn.Close()
  113. return nil, err
  114. }
  115. }
  116. return conn, nil
  117. }))
  118. }
  119. func waitRetry(ctx context.Context, interval time.Duration) error {
  120. timer := time.NewTimer(interval)
  121. defer timer.Stop()
  122. select {
  123. case <-ctx.Done():
  124. return ctx.Err()
  125. case <-timer.C:
  126. return nil
  127. }
  128. }
  129. func UploadFileToFtp(ctx context.Context, localFile, serverAddr, loginUser, loginPass string, timeout time.Duration) (string, error) {
  130. unlock, ok := tryLockFile(localFile)
  131. if !ok {
  132. return "", fmt.Errorf("文件%q正在使用中", localFile)
  133. }
  134. defer unlock()
  135. if ctx == nil {
  136. ctx = context.Background()
  137. }
  138. timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
  139. defer cancel()
  140. if FileUpFolder == "" {
  141. return "", fmt.Errorf("无效的上传目录")
  142. }
  143. lf, err := os.Open(localFile)
  144. if err != nil {
  145. return "", err
  146. }
  147. defer lf.Close()
  148. info, err := lf.Stat()
  149. if err != nil {
  150. return "", err
  151. }
  152. lfs := info.Size() // 总上传的字节数
  153. rd := FileUpFolder // 远程目录名
  154. remoteFile := filepath.Join(rd, filepath.Base(localFile)) // 远程文件名
  155. for {
  156. select {
  157. case <-timeoutCtx.Done():
  158. return "", timeoutCtx.Err()
  159. default:
  160. }
  161. err := func() error {
  162. c, err := dialFTP(timeoutCtx, serverAddr)
  163. if err != nil {
  164. return &stopError{err}
  165. }
  166. defer c.Quit()
  167. if err := c.Login(loginUser, loginPass); err != nil {
  168. return &stopError{err}
  169. }
  170. _ = c.MakeDir(rd) // 尝试创建远程目录, 忽略已存在和其它错误
  171. rfs, err := c.FileSize(remoteFile) // 已上传的字节数
  172. if err != nil || rfs > lfs {
  173. rfs = 0
  174. }
  175. if _, err := lf.Seek(rfs, io.SeekStart); err != nil {
  176. return &stopError{err}
  177. }
  178. pr := newProgressReader(lf, localFile, lfs, rfs, "上传", timeoutCtx)
  179. if err := c.StorFrom(remoteFile, pr, uint64(rfs)); err != nil {
  180. return err
  181. }
  182. return nil
  183. }()
  184. if err != nil {
  185. if lfe, ok := err.(*stopError); ok {
  186. return "", lfe.err
  187. }
  188. if err := waitRetry(timeoutCtx, defaultRtyInterval); err != nil {
  189. return "", err
  190. }
  191. continue
  192. }
  193. return remoteFile, nil
  194. }
  195. }
  196. func DownloadFileFromFtp(ctx context.Context, serverAddr, loginUser, loginPass, remoteFile string, timeout time.Duration) (string, error) {
  197. unlock, ok := tryLockFile(remoteFile)
  198. if !ok {
  199. return "", fmt.Errorf("文件%q正在使用中", remoteFile)
  200. }
  201. defer unlock()
  202. if ctx == nil {
  203. ctx = context.Background()
  204. }
  205. timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
  206. defer cancel()
  207. ld := baseapp.VAR_DIR // 本地目录名
  208. localFile := filepath.Join(ld, filepath.Base(remoteFile)) // 本地文件名
  209. lf, err := os.OpenFile(localFile, os.O_CREATE|os.O_RDWR, 0644)
  210. if err != nil {
  211. return "", err
  212. }
  213. defer lf.Close()
  214. info, err := lf.Stat()
  215. if err != nil {
  216. return "", err
  217. }
  218. lfs := info.Size() // 已下载的字节数
  219. for {
  220. select {
  221. case <-timeoutCtx.Done():
  222. return "", timeoutCtx.Err()
  223. default:
  224. }
  225. err := func() error {
  226. c, err := dialFTP(timeoutCtx, serverAddr)
  227. if err != nil {
  228. return &stopError{err}
  229. }
  230. defer c.Quit()
  231. if err := c.Login(loginUser, loginPass); err != nil {
  232. return &stopError{err}
  233. }
  234. rfs, err := c.FileSize(remoteFile) // 总下载的字节数
  235. if err != nil {
  236. return &stopError{err}
  237. }
  238. if lfs > rfs {
  239. lfs = 0
  240. }
  241. if _, err := lf.Seek(lfs, io.SeekStart); err != nil {
  242. return &stopError{err}
  243. }
  244. resp, err := c.RetrFrom(remoteFile, uint64(lfs))
  245. if err != nil {
  246. return err
  247. }
  248. defer resp.Close()
  249. pr := newProgressReader(resp, remoteFile, rfs, lfs, "下载", timeoutCtx)
  250. n, err := io.Copy(lf, pr)
  251. if err != nil {
  252. return err
  253. }
  254. lfs += n
  255. return nil
  256. }()
  257. if err != nil {
  258. if lfe, ok := err.(*stopError); ok {
  259. return "", lfe.err
  260. }
  261. if err := waitRetry(timeoutCtx, defaultRtyInterval); err != nil {
  262. return "", err
  263. }
  264. continue
  265. }
  266. return localFile, nil
  267. }
  268. }