file_upload.go 2.8 KB

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