158 lines
3.6 KiB
Go
158 lines
3.6 KiB
Go
package photosapi
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const (
|
|
MaxUploadPartSize = 4 << 20 // 4MB
|
|
)
|
|
|
|
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")
|
|
)
|
|
|
|
func (s *Service) UploadCreate(c *gin.Context) {
|
|
sha, err := uuid.NewRandom()
|
|
if err != nil {
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
if err := s.StorageTmp.Create(sha.String()); err != nil {
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"upload_id": sha.String(),
|
|
})
|
|
}
|
|
|
|
type UploadUri struct {
|
|
Id string `uri:"upload_id" binding:"required,uuid"`
|
|
}
|
|
|
|
type UploadPartQuery struct {
|
|
Part uint `form:"part" binding:"required"`
|
|
PartSha256 string `form:"sha256" binding:"required,sha256"`
|
|
}
|
|
|
|
func (s *Service) UploadPart(c *gin.Context) {
|
|
var (
|
|
upload UploadUri
|
|
uploadPart UploadPartQuery
|
|
)
|
|
|
|
if c.BindUri(&upload) != nil || c.BindQuery(&uploadPart) != nil {
|
|
return
|
|
}
|
|
|
|
if !s.StorageTmp.Exists(upload.Id) {
|
|
c.AbortWithError(http.StatusNotFound, ErrUploadNotExists)
|
|
return
|
|
}
|
|
|
|
if c.Request.ContentLength > MaxUploadPartSize {
|
|
c.AbortWithError(http.StatusRequestEntityTooLarge, ErrUploadPartTooLarge)
|
|
return
|
|
}
|
|
|
|
tmp_file := s.StorageTmp.Join(upload.Id, fmt.Sprintf("._tmp_%d", uploadPart.Part))
|
|
file := s.StorageTmp.Join(upload.Id, fmt.Sprint(uploadPart.Part))
|
|
|
|
f, err := os.Create(tmp_file)
|
|
if err != nil {
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
sha := sha256.New()
|
|
t := io.TeeReader(c.Request.Body, sha)
|
|
w, err := io.Copy(f, t)
|
|
if err != nil {
|
|
f.Close()
|
|
os.Remove(tmp_file)
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
f.Close()
|
|
|
|
shastr := hex.EncodeToString(sha.Sum(nil))
|
|
if shastr != uploadPart.PartSha256 {
|
|
os.Remove(tmp_file)
|
|
c.AbortWithError(http.StatusBadRequest, ErrUploadPartWrongSha256)
|
|
return
|
|
}
|
|
|
|
if err = os.Rename(tmp_file, file); err != nil {
|
|
os.Remove(tmp_file)
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"upload_id": upload.Id,
|
|
"part": uploadPart.Part,
|
|
"size": w,
|
|
"sha256": uploadPart.PartSha256,
|
|
})
|
|
}
|
|
|
|
func (s *Service) UploadCancel(c *gin.Context) {
|
|
var upload UploadUri
|
|
if c.BindUri(&upload) != nil {
|
|
return
|
|
}
|
|
|
|
if err := s.StorageTmp.Delete(upload.Id); err != nil {
|
|
c.AbortWithError(http.StatusNotFound, ErrUploadNotExists)
|
|
return
|
|
}
|
|
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
type UploadCompleteRequest struct {
|
|
Sha256 string `json:"sha256" binding:"required,sha256"`
|
|
Name string `json:"name" binding:"required"`
|
|
Parts uint `json:"parts" binding:"required"`
|
|
}
|
|
|
|
func (s *Service) UploadComplete(c *gin.Context) {
|
|
var uploadCompleteRequest UploadCompleteRequest
|
|
if c.BindJSON(&uploadCompleteRequest) != nil {
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"sha256": uploadCompleteRequest.Sha256,
|
|
"parts": uploadCompleteRequest.Parts,
|
|
"name": uploadCompleteRequest.Name,
|
|
})
|
|
}
|
|
|
|
func (s *Service) UploadInit() {
|
|
upload := s.Gin.Group("/upload")
|
|
|
|
// upload.GET("/create", s.UploadCreate)
|
|
// upload.POST("/part/:upload_id", s.UploadPart)
|
|
// upload.GET("/cancel/:upload_id", s.UploadCancel)
|
|
// upload.POST("/complete/:upload_id", s.UploadComplete)
|
|
|
|
upload.POST("", s.UploadCreate)
|
|
upload.DELETE("/:upload_id", s.UploadCancel)
|
|
upload.PUT("/:upload_id", s.UploadPart)
|
|
upload.POST("/:upload_id", s.UploadComplete)
|
|
}
|