146 lines
3.0 KiB
Go
146 lines
3.0 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,
|
|
})
|
|
}
|