185 lines
3.8 KiB
Go
185 lines
3.8 KiB
Go
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:1], checksum[1:2], checksum)
|
|
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
|
|
}
|
|
dir, err := s.StoreDir(originalChecksum)
|
|
if err != nil {
|
|
s.Error(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
data, err := os.Create(filepath.Join(dir, ".tmp"))
|
|
if err != nil {
|
|
s.Error(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
defer data.Close()
|
|
|
|
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
|
|
}
|
|
_, err = sum.Write(b)
|
|
if err != nil {
|
|
s.Error(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
_, err = data.Write(b)
|
|
if err != nil {
|
|
s.Error(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
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",
|
|
})
|
|
}
|