package api import ( "bytes" "errors" "io" "net/http" "strings" "github.com/gin-gonic/gin" "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 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) if err := s.Store.NewChunk(b.Bytes()).Save(sess); err != nil { if errors.Is(err, photoserrors.ErrStoreChunkAlreadyExists) { s.Error(c, http.StatusOK, err) } else { s.Error(c, http.StatusBadRequest, err) } return } c.JSON(http.StatusOK, gin.H{ "status": "success", }) }