Skip to content

Commit cdb4392

Browse files
adfostbexsoftdvaldivia
authored
Adding Download Directory to Console (#1235)
* Adding Download Directory Functionality * fixing spaces * removing unnecessary function Co-authored-by: Alex <[email protected]> Co-authored-by: Daniel Valdivia <[email protected]>
1 parent 41f77f1 commit cdb4392

File tree

2 files changed

+67
-36
lines changed

2 files changed

+67
-36
lines changed

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ const ListObjects = ({
758758
return true;
759759
}
760760
}
761-
return item.endsWith("/");
761+
return false;
762762
},
763763
sendOnlyId: false,
764764
},

restapi/user_objects.go

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package restapi
1818

1919
import (
20+
"archive/zip"
21+
"bytes"
2022
"context"
2123
"encoding/base64"
2224
"fmt"
@@ -96,40 +98,51 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
9698
prefixPath = string(decodedPrefix)
9799
}
98100
prefixElements := strings.Split(prefixPath, "/")
101+
isFolder := false
99102
if len(prefixElements) > 0 {
100-
filename = prefixElements[len(prefixElements)-1]
103+
if prefixElements[len(prefixElements)-1] == "" {
104+
filename = prefixElements[len(prefixElements)-2]
105+
isFolder = true
106+
} else {
107+
filename = prefixElements[len(prefixElements)-1]
108+
}
101109
}
102110
if isPreview {
103111
rw.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename))
104112
rw.Header().Set("X-Frame-Options", "SAMEORIGIN")
105113
rw.Header().Set("X-XSS-Protection", "1")
106114

115+
} else if isFolder {
116+
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filename))
117+
rw.Header().Set("Content-Type", "application/zip")
107118
} else {
108119
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
109120
rw.Header().Set("Content-Type", "application/octet-stream")
110121
}
111122

112123
// indicate object size & content type
113-
stat, err := resp.(*minio.Object).Stat()
114-
if err != nil {
115-
log.Println(err)
116-
} else {
117-
rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
124+
if !isFolder {
125+
stat, err := resp.(*minio.Object).Stat()
126+
if err != nil {
127+
log.Println(err)
128+
} else {
129+
rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
118130

119-
contentType := stat.ContentType
131+
contentType := stat.ContentType
120132

121-
if isPreview {
122-
// In case content type was uploaded as octet-stream, we double verify content type
123-
if stat.ContentType == "application/octet-stream" {
124-
contentType = mimedb.TypeByExtension(filepath.Ext(filename))
133+
if isPreview {
134+
// In case content type was uploaded as octet-stream, we double verify content type
135+
if stat.ContentType == "application/octet-stream" {
136+
contentType = mimedb.TypeByExtension(filepath.Ext(filename))
137+
}
125138
}
126-
}
127139

128-
rw.Header().Set("Content-Type", contentType)
140+
rw.Header().Set("Content-Type", contentType)
141+
}
129142
}
130143

131144
// Copy the stream
132-
_, err = io.Copy(rw, resp)
145+
_, err := io.Copy(rw, resp)
133146
if err != nil {
134147
log.Println(err)
135148
}
@@ -305,6 +318,7 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
305318
func getDownloadObjectResponse(session *models.Principal, params user_api.DownloadObjectParams) (io.ReadCloser, *models.Error) {
306319
ctx := context.Background()
307320
var prefix string
321+
mClient, err := newMinioClient(session)
308322
if params.Prefix != "" {
309323
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
310324
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
@@ -313,34 +327,51 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo
313327
}
314328
prefix = string(decodedPrefix)
315329
}
316-
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
317-
if err != nil {
318-
return nil, prepareError(err)
330+
isFolder := false
331+
folders := strings.Split(prefix, "/")
332+
if folders[len(folders)-1] == "" {
333+
isFolder = true
319334
}
320-
// create a mc S3Client interface implementation
321-
// defining the client to be used
322-
mcClient := mcClient{client: s3Client}
323-
object, err := downloadObject(ctx, mcClient, params.VersionID)
335+
if isFolder {
336+
if err != nil {
337+
return nil, prepareError(err)
338+
}
339+
minioClient := minioClient{client: mClient}
340+
objects, err := listBucketObjects(ctx, minioClient, params.BucketName, prefix, true, false, false)
341+
if err != nil {
342+
return nil, prepareError(err)
343+
}
344+
w := new(bytes.Buffer)
345+
zipw := zip.NewWriter(w)
346+
var folder string
347+
if len(folders) > 1 {
348+
folder = folders[len(folders)-2]
349+
}
350+
for i := 0; i < len(objects); i++ {
351+
name := folder + objects[i].Name[len(prefix)-1:]
352+
object, err := mClient.GetObject(ctx, params.BucketName, objects[i].Name, minio.GetObjectOptions{})
353+
if err != nil {
354+
return nil, prepareError(err)
355+
}
356+
f, err := zipw.Create(name)
357+
if err != nil {
358+
return nil, prepareError(err)
359+
}
360+
buf := new(bytes.Buffer)
361+
buf.ReadFrom(object)
362+
f.Write(buf.Bytes())
363+
}
364+
zipw.Close()
365+
zipfile := io.NopCloser(bytes.NewReader(w.Bytes()))
366+
return zipfile, nil
367+
}
368+
object, err := mClient.GetObject(ctx, params.BucketName, prefix, minio.GetObjectOptions{})
324369
if err != nil {
325370
return nil, prepareError(err)
326371
}
327372
return object, nil
328373
}
329374

330-
func downloadObject(ctx context.Context, client MCClient, versionID *string) (io.ReadCloser, error) {
331-
// TODO: handle encrypted files
332-
var reader io.ReadCloser
333-
var version string
334-
if versionID != nil {
335-
version = *versionID
336-
}
337-
reader, pErr := client.get(ctx, mc.GetOptions{VersionID: version})
338-
if pErr != nil {
339-
return nil, pErr.Cause
340-
}
341-
return reader, nil
342-
}
343-
344375
// getDeleteObjectResponse returns whether there was an error on deletion of object
345376
func getDeleteObjectResponse(session *models.Principal, params user_api.DeleteObjectParams) *models.Error {
346377
ctx := context.Background()

0 commit comments

Comments
 (0)