2022-03-05 17:21:24 +01:00

166 lines
3.5 KiB
Go

package api
import (
"bytes"
"errors"
"io"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/go-sql-driver/mysql"
"gitlab.celogeek.com/photos/api/internal/photos/models"
"gitlab.celogeek.com/photos/api/internal/photoserrors"
"gorm.io/gorm"
)
var CHUNK_SIZE int64 = 1 << 20
type File struct {
Name string `json:"name"`
Checksum string `json:"checksum"`
Chunks []string `json:"chunks"`
}
func (s *Service) FileCreate(c *gin.Context) {
file := &File{}
if err := c.ShouldBindJSON(file); err != nil {
s.Error(c, http.StatusInternalServerError, err)
return
}
if len(file.Name) < 1 {
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreMissingName)
return
}
if len(file.Checksum) != 40 {
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
return
}
if len(file.Chunks) == 0 {
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreMissingChunks)
return
}
for _, chunk := range file.Chunks {
if len(chunk) != 40 {
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
return
}
}
r, rs, err := s.Store.Combine(file.Chunks)
if err != nil {
if strings.HasPrefix(err.Error(), "chunk") && strings.HasSuffix(err.Error(), "doesn't exists") {
s.Error(c, http.StatusBadRequest, err)
} else {
s.Error(c, http.StatusInternalServerError, err)
}
return
}
if r != file.Checksum {
s.Error(c, http.StatusExpectationFailed, photoserrors.ErrStoreMismatchChecksum)
return
}
sess := s.CurrentSession(c)
f := &models.File{
Name: file.Name,
Checksum: file.Checksum,
Size: rs,
AuthorId: &sess.AccountId,
}
err = s.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(f).Error; err != nil {
return err
}
for i, chunk := range file.Chunks {
fc := &models.FileChunk{
FileId: f.ID,
Part: uint32(i + 1),
Checksum: chunk,
}
if err := tx.Create(fc).Error; err != nil {
return err
}
}
return nil
})
if nerr, ok := err.(*mysql.MySQLError); ok {
if nerr.Number == 1062 {
// duplicate error
if strings.HasSuffix(nerr.Message, "for key 'checksum'") {
err = nil
}
}
}
if err != nil {
s.Error(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, gin.H{
"status": "success",
"sum": file.Checksum,
"nbChunks": len(file.Chunks),
"size": rs,
})
}
func (s *Service) FileCreateChunk(c *gin.Context) {
if c.Request.ContentLength > CHUNK_SIZE {
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChunkSize)
return
}
b := bytes.NewBuffer([]byte{})
io.Copy(b, c.Request.Body)
c.Request.Body.Close()
sess := s.CurrentSession(c)
chunk := s.Store.NewChunk(b.Bytes())
if err := chunk.Save(sess); err != nil {
if errors.Is(err, photoserrors.ErrStoreChunkAlreadyExists) {
c.JSON(http.StatusOK, gin.H{
"status": "success",
"checksum": chunk.Sum,
})
} else {
s.Error(c, http.StatusBadRequest, err)
}
return
}
c.JSON(http.StatusOK, gin.H{
"status": "success",
"checksum": chunk.Sum,
})
}
func (s *Service) FileExists(c *gin.Context) {
checksum := c.Param("checksum")
if len(checksum) != 40 {
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
return
}
var fileExists int64
if err := s.DB.Model(&models.File{}).Where("checksum = ?", checksum).Count(&fileExists).Error; err != nil {
s.Error(c, http.StatusInternalServerError, err)
return
}
if fileExists > 0 {
c.Status(http.StatusOK)
} else {
c.Status(http.StatusNotFound)
}
}