package api import ( "bytes" "crypto/sha1" "encoding/hex" "fmt" "io" "io/fs" "net/http" "os" "path/filepath" "sort" "strconv" "github.com/gin-gonic/gin" ) var CHUNK_SIZE int64 = 1 << 20 func (s *Service) PrepareStore() { d, err := os.Stat(s.Config.StorePath) if err != nil { s.LogErr.Fatal("Store", err) } if !d.IsDir() { s.LogErr.Fatal("Store", ErrStorePathNotADirectory) } } func (s *Service) StoreDir(checksum string) (string, error) { dir := filepath.Join(s.Config.StorePath, "original", checksum[0:0], checksum[1:1], checksum[2:]) err := os.MkdirAll(dir, 0755) return dir, err } func (s *Service) TempDir(checksum string) (string, error) { dir := filepath.Join(s.Config.StorePath, "tmp", checksum) err := os.MkdirAll(dir, 0755) return dir, err } type FileChunks struct { N uint64 Path fs.FS Name string } func (s *Service) FileChunks(checksum string) ([]FileChunks, error) { base := filepath.Join(s.Config.StorePath, "tmp", checksum) baseDir := os.DirFS(base) dir, err := os.Open(base) if err != nil { return nil, err } defer dir.Close() files, err := dir.Readdirnames(-1) if err != nil { return nil, err } parts := []FileChunks{} for _, f := range files { n, err := strconv.ParseUint(f, 10, 64) if err != nil { continue } parts = append(parts, FileChunks{n, baseDir, f}) } sort.Slice(parts, func(i, j int) bool { return parts[i].N < parts[j].N }) return parts, nil } func (s *Service) FileCreate(c *gin.Context) { var originalChecksum = c.Param("original_checksum") files, err := s.FileChunks(originalChecksum) if err != nil { s.Error(c, http.StatusInternalServerError, err) return } sum := sha1.New() size := uint64(0) for _, f := range files { b, err := fs.ReadFile(f.Path, f.Name) if err != nil { s.Error(c, http.StatusInternalServerError, err) return } sum.Write(b) size += uint64(len(b)) } r := hex.EncodeToString(sum.Sum(nil)) if r != originalChecksum { fmt.Printf("R=%s, O=%s\n", r, originalChecksum) s.Error(c, http.StatusExpectationFailed, ErrStoreMismatchChecksum) return } c.JSON(http.StatusOK, gin.H{ "status": "success", "checksum": originalChecksum, "nbParts": len(files), "size": size, }) } func (s *Service) FileChunk(c *gin.Context) { var ( originalChecksum = c.Param("original_checksum") part = c.Param("part") partChecksum = c.Param("part_checksum") ) if len(originalChecksum) != 40 || len(partChecksum) != 40 { s.Error(c, http.StatusBadRequest, ErrStoreBadChecksum) return } if c.Request.ContentLength > CHUNK_SIZE { s.Error(c, http.StatusBadRequest, ErrStoreBadChunkSize) return } p, err := strconv.ParseUint(part, 10, 64) if err != nil || p < 1 { s.Error(c, http.StatusBadRequest, ErrStoreBadPartNumber) return } b := bytes.NewBuffer([]byte{}) io.Copy(b, c.Request.Body) c.Request.Body.Close() sum := sha1.New() sum.Write(b.Bytes()) r := hex.EncodeToString(sum.Sum(nil)) if partChecksum != r { s.Error(c, http.StatusBadRequest, ErrStoreWrongPartChecksum) return } dir, err := s.TempDir(originalChecksum) if err != nil { s.Error(c, http.StatusInternalServerError, err) return } f, err := os.Create(filepath.Join(dir, part)) if err != nil { s.Error(c, http.StatusInternalServerError, err) return } f.Write(b.Bytes()) f.Close() c.JSON(http.StatusOK, gin.H{ "status": "success", }) }