From cdb43926336adb5ef6ebc81275c14640f94eb805 Mon Sep 17 00:00:00 2001 From: adfost Date: Wed, 24 Nov 2021 20:08:25 -0800 Subject: [PATCH] Adding Download Directory to Console (#1235) * Adding Download Directory Functionality * fixing spaces * removing unnecessary function Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com> Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> --- .../Objects/ListObjects/ListObjects.tsx | 2 +- restapi/user_objects.go | 101 ++++++++++++------ 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx index 56b24c2ce..9b27645fb 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx @@ -758,7 +758,7 @@ const ListObjects = ({ return true; } } - return item.endsWith("/"); + return false; }, sendOnlyId: false, }, diff --git a/restapi/user_objects.go b/restapi/user_objects.go index 385c76aff..c0e5266de 100644 --- a/restapi/user_objects.go +++ b/restapi/user_objects.go @@ -17,6 +17,8 @@ package restapi import ( + "archive/zip" + "bytes" "context" "encoding/base64" "fmt" @@ -96,40 +98,51 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) { prefixPath = string(decodedPrefix) } prefixElements := strings.Split(prefixPath, "/") + isFolder := false if len(prefixElements) > 0 { - filename = prefixElements[len(prefixElements)-1] + if prefixElements[len(prefixElements)-1] == "" { + filename = prefixElements[len(prefixElements)-2] + isFolder = true + } else { + filename = prefixElements[len(prefixElements)-1] + } } if isPreview { rw.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename)) rw.Header().Set("X-Frame-Options", "SAMEORIGIN") rw.Header().Set("X-XSS-Protection", "1") + } else if isFolder { + rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filename)) + rw.Header().Set("Content-Type", "application/zip") } else { rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) rw.Header().Set("Content-Type", "application/octet-stream") } // indicate object size & content type - stat, err := resp.(*minio.Object).Stat() - if err != nil { - log.Println(err) - } else { - rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size)) + if !isFolder { + stat, err := resp.(*minio.Object).Stat() + if err != nil { + log.Println(err) + } else { + rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size)) - contentType := stat.ContentType + contentType := stat.ContentType - if isPreview { - // In case content type was uploaded as octet-stream, we double verify content type - if stat.ContentType == "application/octet-stream" { - contentType = mimedb.TypeByExtension(filepath.Ext(filename)) + if isPreview { + // In case content type was uploaded as octet-stream, we double verify content type + if stat.ContentType == "application/octet-stream" { + contentType = mimedb.TypeByExtension(filepath.Ext(filename)) + } } - } - rw.Header().Set("Content-Type", contentType) + rw.Header().Set("Content-Type", contentType) + } } // Copy the stream - _, err = io.Copy(rw, resp) + _, err := io.Copy(rw, resp) if err != nil { log.Println(err) } @@ -305,6 +318,7 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin func getDownloadObjectResponse(session *models.Principal, params user_api.DownloadObjectParams) (io.ReadCloser, *models.Error) { ctx := context.Background() var prefix string + mClient, err := newMinioClient(session) if params.Prefix != "" { encodedPrefix := SanitizeEncodedPrefix(params.Prefix) decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) @@ -313,34 +327,51 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo } prefix = string(decodedPrefix) } - s3Client, err := newS3BucketClient(session, params.BucketName, prefix) - if err != nil { - return nil, prepareError(err) + isFolder := false + folders := strings.Split(prefix, "/") + if folders[len(folders)-1] == "" { + isFolder = true } - // create a mc S3Client interface implementation - // defining the client to be used - mcClient := mcClient{client: s3Client} - object, err := downloadObject(ctx, mcClient, params.VersionID) + if isFolder { + if err != nil { + return nil, prepareError(err) + } + minioClient := minioClient{client: mClient} + objects, err := listBucketObjects(ctx, minioClient, params.BucketName, prefix, true, false, false) + if err != nil { + return nil, prepareError(err) + } + w := new(bytes.Buffer) + zipw := zip.NewWriter(w) + var folder string + if len(folders) > 1 { + folder = folders[len(folders)-2] + } + for i := 0; i < len(objects); i++ { + name := folder + objects[i].Name[len(prefix)-1:] + object, err := mClient.GetObject(ctx, params.BucketName, objects[i].Name, minio.GetObjectOptions{}) + if err != nil { + return nil, prepareError(err) + } + f, err := zipw.Create(name) + if err != nil { + return nil, prepareError(err) + } + buf := new(bytes.Buffer) + buf.ReadFrom(object) + f.Write(buf.Bytes()) + } + zipw.Close() + zipfile := io.NopCloser(bytes.NewReader(w.Bytes())) + return zipfile, nil + } + object, err := mClient.GetObject(ctx, params.BucketName, prefix, minio.GetObjectOptions{}) if err != nil { return nil, prepareError(err) } return object, nil } -func downloadObject(ctx context.Context, client MCClient, versionID *string) (io.ReadCloser, error) { - // TODO: handle encrypted files - var reader io.ReadCloser - var version string - if versionID != nil { - version = *versionID - } - reader, pErr := client.get(ctx, mc.GetOptions{VersionID: version}) - if pErr != nil { - return nil, pErr.Cause - } - return reader, nil -} - // getDeleteObjectResponse returns whether there was an error on deletion of object func getDeleteObjectResponse(session *models.Principal, params user_api.DeleteObjectParams) *models.Error { ctx := context.Background()