Skip to content

Commit

Permalink
feat(chunk): Starting to add chunk uploader
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent Boutour <[email protected]>
  • Loading branch information
ViBiOh committed Jul 15, 2022
1 parent 49cb68a commit a296fd0
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 16 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ ENTRYPOINT [ "/fibr" ]

ARG VERSION
ENV VERSION ${VERSION}
ENV FIBR_VERSION ${VERSION}

VOLUME /tmp

ARG TARGETOS
ARG TARGETARCH
Expand Down
146 changes: 146 additions & 0 deletions pkg/crud/chunk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package crud

import (
"context"
"fmt"
"io"
"io/fs"
"mime/multipart"
"net/http"
"os"
"path/filepath"

"github.com/ViBiOh/fibr/pkg/provider"
"github.com/ViBiOh/httputils/v4/pkg/logger"
"github.com/ViBiOh/httputils/v4/pkg/model"
"github.com/ViBiOh/httputils/v4/pkg/sha"
)

// UploadChunk save chunk file to a temp file
func (a App) UploadChunk(w http.ResponseWriter, r *http.Request, request provider.Request, values map[string]string, file *multipart.Part) {
var err error

tempDestination := filepath.Join(temporaryFolder, sha.New(values["filename"]))
tempFile := filepath.Join(tempDestination, values["chunkNumber"])

if err = os.MkdirAll(tempDestination, 0o700); err != nil {
a.error(w, r, request, model.WrapInternal(err))
return
}

var writer *os.File
writer, err = os.OpenFile(tempFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return
}

defer func() {
if closeErr := writer.Close(); closeErr != nil {
logger.Error("unable to close chunk writer: %s", closeErr)
}

if err == nil {
return
}

if removeErr := os.Remove(tempFile); removeErr != nil {
logger.Error("unable to remove chunk file `%s`: %s", tempFile, removeErr)
}
}()

if _, err = io.Copy(writer, file); err != nil {
a.error(w, r, request, model.WrapInternal(err))
return
}

w.WriteHeader(http.StatusAccepted)
}

// MergeChunk merges previously uploaded chunks into one file and move it to final destination
func (a App) MergeChunk(w http.ResponseWriter, r *http.Request, request provider.Request, values map[string]string) {
var err error

tempFolder := filepath.Join(temporaryFolder, sha.New(values["filename"]))
tempFile := filepath.Join(tempFolder, values["filename"])

if err := a.mergeChunkFiles(tempFolder, tempFile); err != nil {
a.error(w, r, request, model.WrapInternal(err))
return
}

var size int64
size, err = getUploadSize(values["size"])
if err != nil {
a.error(w, r, request, model.WrapInternal(err))
return
}

file, err := os.Open(tempFile)
if err != nil {
a.error(w, r, request, model.WrapInternal(err))
return
}

filePath := request.SubPath(values["filename"])
err = provider.WriteToStorage(r.Context(), a.storageApp, filePath, size, file)

if err == nil {
go func() {
if info, infoErr := a.storageApp.Info(context.Background(), filePath); infoErr != nil {
logger.Error("unable to get info for upload event: %s", infoErr)
} else {
a.notify(provider.NewUploadEvent(request, info, a.bestSharePath(filePath), a.rendererApp))
}
}()
}

w.WriteHeader(http.StatusCreated)
}

func (a App) mergeChunkFiles(directory, destination string) error {
writer, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return fmt.Errorf("unable to open destination file `%s`: %s", destination, err)
}

defer func() {
if closeErr := writer.Close(); closeErr != nil {
logger.Error("unable to close chunk's destination: %s", closeErr)
}

if err == nil {
return
}

if removeErr := os.Remove(destination); removeErr != nil {
logger.Error("unable to remove chunk's destination `%s`: %s", destination, removeErr)
}
}()

if err = filepath.WalkDir(directory, func(path string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}

reader, err := os.Open(path)
if err != nil {
return fmt.Errorf("unable to open chunk `%s`: %s", path, err)
}

defer func() {
if closeErr := reader.Close(); closeErr != nil {
logger.Error("unable to close chunk `%s`: %s", path, err)
}
}()

if _, err = io.Copy(writer, reader); err != nil {
return fmt.Errorf("unable to copy chunk `%s`: %s", path, err)
}

return nil
}); err != nil {
return fmt.Errorf("unable to walk chunks in `%s`: %s", directory, err)
}

return nil
}
32 changes: 17 additions & 15 deletions pkg/crud/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import (
"github.com/ViBiOh/httputils/v4/pkg/renderer"
)

func (a App) saveUploadedFile(ctx context.Context, request provider.Request, inputName, rawSize string, part *multipart.Part) (filename string, err error) {
var filepath string
const temporaryFolder = "/tmp"

filename, filepath, err = getUploadNameAndPath(request, inputName, part)
func (a App) saveUploadedFile(ctx context.Context, request provider.Request, inputName, rawSize string, file *multipart.Part) (fileName string, err error) {
var filePath string

fileName, filePath, err = getUploadNameAndPath(request, inputName, file)
if err != nil {
return "", fmt.Errorf("unable to get upload name: %s", err)
}
Expand All @@ -29,37 +31,37 @@ func (a App) saveUploadedFile(ctx context.Context, request provider.Request, inp
return "", fmt.Errorf("unable to get upload size: %s", err)
}

err = provider.WriteToStorage(ctx, a.storageApp, filepath, size, part)
err = provider.WriteToStorage(ctx, a.storageApp, filePath, size, file)

if err == nil {
go func() {
if info, infoErr := a.storageApp.Info(context.Background(), filepath); infoErr != nil {
if info, infoErr := a.storageApp.Info(context.Background(), filePath); infoErr != nil {
logger.Error("unable to get info for upload event: %s", infoErr)
} else {
a.notify(provider.NewUploadEvent(request, info, a.bestSharePath(filepath), a.rendererApp))
a.notify(provider.NewUploadEvent(request, info, a.bestSharePath(filePath), a.rendererApp))
}
}()
}

return filename, err
return fileName, err
}

func getUploadNameAndPath(request provider.Request, inputName string, part *multipart.Part) (filename string, filepath string, err error) {
func getUploadNameAndPath(request provider.Request, inputName string, part *multipart.Part) (fileName string, filePath string, err error) {
if !request.Share.IsZero() && request.Share.File {
return path.Base(request.Share.Path), request.Share.Path, nil
}

if len(inputName) != 0 {
filename = inputName
fileName = inputName
} else {
filename = part.FileName()
fileName = part.FileName()
}

filename, err = provider.SanitizeName(filename, true)
fileName, err = provider.SanitizeName(fileName, true)
if err != nil {
return
}
filepath = request.SubPath(filename)
filePath = request.SubPath(fileName)

return
}
Expand All @@ -77,13 +79,13 @@ func getUploadSize(rawSize string) (int64, error) {
}

// Upload saves form files to filesystem
func (a App) Upload(w http.ResponseWriter, r *http.Request, request provider.Request, values map[string]string, part *multipart.Part) {
func (a App) Upload(w http.ResponseWriter, r *http.Request, request provider.Request, values map[string]string, file *multipart.Part) {
if !request.CanEdit {
a.error(w, r, request, model.WrapForbidden(ErrNotAuthorized))
return
}

if part == nil {
if file == nil {
a.error(w, r, request, model.WrapInvalid(errors.New("no file provided for save")))
return
}
Expand All @@ -102,7 +104,7 @@ func (a App) Upload(w http.ResponseWriter, r *http.Request, request provider.Req

ctx := r.Context()

filename, err := a.saveUploadedFile(ctx, request, values["filename"], values["size"], part)
filename, err := a.saveUploadedFile(ctx, request, values["filename"], values["size"], file)
if err != nil {
a.error(w, r, request, model.WrapInternal(err))
return
Expand Down

0 comments on commit a296fd0

Please sign in to comment.