From 0bd48fa1665f5831e6c28be81810c82491503c0f Mon Sep 17 00:00:00 2001 From: celogeek Date: Sun, 15 May 2022 00:19:45 +0200 Subject: [PATCH] move on on complete upload --- internal/photos/api/upload.go | 85 ++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/internal/photos/api/upload.go b/internal/photos/api/upload.go index b81abf8..baea353 100644 --- a/internal/photos/api/upload.go +++ b/internal/photos/api/upload.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "os" + "strings" "time" "github.com/gin-gonic/gin" @@ -19,10 +20,11 @@ const ( ) var ( - ErrUploadNotExists = errors.New("upload id doesn't exists") - ErrUploadPartTooLarge = fmt.Errorf("upload part too large (> %d B)", MaxUploadPartSize) - ErrUploadPartWrongSha256 = errors.New("upload part wrong sha256") - ErrFileAlreadExists = errors.New("file already exists") + ErrUploadNotExists = errors.New("upload id does not exists") + ErrUploadPartTooLarge = fmt.Errorf("upload part too large (> %d B)", MaxUploadPartSize) + ErrUploadPartWrongSha256 = errors.New("upload part sha256 does not match") + ErrFileAlreadExists = errors.New("file already exists") + ErrUploadPartsCombineWrongSha256 = errors.New("upload parts combined sha256 does not match") ) // Model @@ -71,6 +73,36 @@ type UploadCompleteRequest struct { Parts uint `json:"parts" binding:"required"` } +type UploadCompleteFileOptions struct { + Ext string + IsTemp bool +} + +func (u *UploadCompleteRequest) Paths() []string { + return []string{u.Sha256[0:1], u.Sha256[1:2]} +} + +func (u *UploadCompleteRequest) File(options *UploadCompleteFileOptions) []string { + filename := []string{} + if options == nil { + options = &UploadCompleteFileOptions{} + } + + if options.IsTemp { + filename = append(filename, "._tmp_") + } + filename = append(filename, u.Sha256) + if len(options.Ext) > 0 { + filename = append(filename, ".", options.Ext) + } + + return []string{ + u.Sha256[0:1], + u.Sha256[1:2], + strings.Join(filename, ""), + } +} + func (s *Service) UploadPart(c *gin.Context) { var ( upload Upload @@ -156,13 +188,52 @@ func (s *Service) UploadComplete(c *gin.Context) { return } - f, err := s.StorageUpload.Stat(uploadCompleteRequest.Sha256[0:1], uploadCompleteRequest.Sha256[1:2], uploadCompleteRequest.Sha256) - fmt.Println(err) - if err == nil && f.Mode().IsRegular() { + if f, err := s.StorageUpload.Stat(uploadCompleteRequest.File(nil)...); err == nil && f.Mode().IsRegular() { c.AbortWithError(http.StatusConflict, ErrFileAlreadExists) return } + if err := s.StorageUpload.Create(uploadCompleteRequest.Paths()...); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + f, err := os.Create(s.StorageUpload.Join(uploadCompleteRequest.File(&UploadCompleteFileOptions{IsTemp: true})...)) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + fsha := sha256.New() + ft := io.MultiWriter(f, fsha) + + defer f.Close() + for part := uint(1); part <= uploadCompleteRequest.Parts; part++ { + p, err := os.Open(s.StorageTmp.Join(upload.Id, fmt.Sprint(part))) + if err != nil { + c.AbortWithError(http.StatusNotFound, fmt.Errorf("upload part %d missing", part)) + return + } + _, err = io.Copy(ft, p) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + } + + if uploadCompleteRequest.Sha256 != hex.EncodeToString(fsha.Sum(nil)) { + c.AbortWithError(http.StatusExpectationFailed, ErrUploadPartsCombineWrongSha256) + return + } + + f.Close() + if err := os.Rename( + s.StorageUpload.Join(uploadCompleteRequest.File(&UploadCompleteFileOptions{IsTemp: true})...), + s.StorageUpload.Join(uploadCompleteRequest.File(nil)...), + ); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + c.Status(http.StatusNoContent) }