file_upload.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package service
  2. import (
  3. "fmt"
  4. "io"
  5. "mime/multipart"
  6. "os"
  7. "sync"
  8. "github.com/labstack/echo/v4"
  9. )
  10. type FileInfo struct {
  11. init bool
  12. uploaded []bool
  13. uploadedChunkNum int64
  14. }
  15. type FileUploadService struct {
  16. uploadStatus sync.Map
  17. lock sync.RWMutex
  18. }
  19. func NewFileUploadService() *FileUploadService {
  20. return &FileUploadService{
  21. uploadStatus: sync.Map{},
  22. lock: sync.RWMutex{},
  23. }
  24. }
  25. func (s *FileUploadService) TestChunk(
  26. c echo.Context,
  27. identifier string,
  28. chunkNumber int64,
  29. ) error {
  30. fileInfoTemp, ok := s.uploadStatus.Load(identifier)
  31. if !ok {
  32. return fmt.Errorf("file not found")
  33. }
  34. fileInfo := fileInfoTemp.(*FileInfo)
  35. if !fileInfo.init {
  36. return fmt.Errorf("file not init")
  37. }
  38. // return StatusNoContent instead of 404
  39. // the is require by frontend
  40. if !fileInfo.uploaded[chunkNumber-1] {
  41. return fmt.Errorf("file not found")
  42. }
  43. return nil
  44. }
  45. func (s *FileUploadService) UploadFile(
  46. c echo.Context,
  47. path string,
  48. chunkNumber int64,
  49. chunkSize int64,
  50. currentChunkSize int64,
  51. totalChunks int64,
  52. totalSize int64,
  53. identifier string,
  54. fileName string,
  55. bin *multipart.FileHeader,
  56. ) error {
  57. s.lock.Lock()
  58. fileInfoTemp, ok := s.uploadStatus.Load(identifier)
  59. var fileInfo *FileInfo
  60. file, err := os.OpenFile(path+"/"+fileName+".tmp", os.O_WRONLY|os.O_CREATE, 0644)
  61. if err != nil {
  62. s.lock.Unlock()
  63. return err
  64. }
  65. if !ok {
  66. if err != nil {
  67. s.lock.Unlock()
  68. return err
  69. }
  70. // pre allocate file size
  71. fmt.Println("truncate", totalSize)
  72. if err != nil {
  73. s.lock.Unlock()
  74. return err
  75. }
  76. // file info init
  77. fileInfo = &FileInfo{
  78. init: true,
  79. uploaded: make([]bool, totalChunks),
  80. uploadedChunkNum: 0,
  81. }
  82. s.uploadStatus.Store(identifier, fileInfo)
  83. } else {
  84. fileInfo = fileInfoTemp.(*FileInfo)
  85. }
  86. s.lock.Unlock()
  87. _, err = file.Seek((chunkNumber-1)*chunkSize, io.SeekStart)
  88. if err != nil {
  89. return err
  90. }
  91. src, err := bin.Open()
  92. if err != nil {
  93. return err
  94. }
  95. defer src.Close()
  96. buf := make([]byte, int(currentChunkSize))
  97. _, err = io.CopyBuffer(file, src, buf)
  98. if err != nil {
  99. fmt.Println(err)
  100. return err
  101. }
  102. s.lock.Lock()
  103. // handle file after write a chunk
  104. // handle single chunk upload twice
  105. if !fileInfo.uploaded[chunkNumber-1] {
  106. fileInfo.uploadedChunkNum++
  107. fileInfo.uploaded[chunkNumber-1] = true
  108. }
  109. // handle file after write all chunk
  110. if fileInfo.uploadedChunkNum == totalChunks {
  111. file.Close()
  112. os.Rename(path+"/"+fileName+".tmp", path+"/"+fileName)
  113. // remove upload status info after upload complete
  114. s.uploadStatus.Delete(identifier)
  115. }
  116. s.lock.Unlock()
  117. return nil
  118. }