Skip to content

Commit

Permalink
Add support for resource id when requesting a download of an archive …
Browse files Browse the repository at this point in the history
…and changed the interface of the archvier service, to accept a list of `path` and `id`
  • Loading branch information
gmgigi96 committed Sep 23, 2021
1 parent e96fa87 commit f321bbb
Showing 1 changed file with 101 additions and 23 deletions.
124 changes: 101 additions & 23 deletions internal/http/services/archiver/archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"io"
"net/http"
"path"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -62,7 +63,7 @@ type Config struct {
MaxSize int64 `mapstructure:"max_size"`
}

var (
const (
errMaxFileCount = errtypes.InternalError("reached max files count")
errMaxSize = errtypes.InternalError("reached max total files size")
)
Expand Down Expand Up @@ -105,38 +106,73 @@ func (c *Config) init() {
c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc)
}

func (s *svc) getFiles(ctx context.Context, files, ids []string) ([]string, error) {
if len(files) == 0 && len(ids) == 0 {
return nil, errtypes.BadRequest("file and id lists are both empty")
}

var f []string

for _, id := range ids {
// an id must have the form <storage_id>:<id>

split := strings.Split(id, ":")
if len(split) != 2 {
return nil, errtypes.BadRequest("id not valid: " + id)
}

resp, err := s.gtwClient.Stat(ctx, &provider.StatRequest{
Ref: &provider.Reference{
ResourceId: &provider.ResourceId{
StorageId: split[0],
OpaqueId: split[1],
},
},
})

switch {
case err != nil:
return nil, err
case resp.Status.Code == rpc.Code_CODE_NOT_FOUND:
return nil, errtypes.NotFound(id)
case resp.Status.Code != rpc.Code_CODE_OK:
return nil, errtypes.InternalError(fmt.Sprintf("error getting stats from %s", id))
}

f = append(f, resp.Info.Path)

}

return append(f, files...), nil
}

func (s *svc) Handler() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// get the dir and files to archive from the URL
// get the paths and/or the resources id from the query
ctx := r.Context()
v := r.URL.Query()
if _, ok := v["dir"]; !ok {
rw.WriteHeader(http.StatusBadRequest)
return
}
dir := v["dir"][0]

names, ok := v["file"]
file, ok := v["file"]
if !ok {
file = []string{}
}
id, ok := v["id"]
if !ok {
names = []string{}
id = []string{}
}

// append to the files name the dir
files := []string{}
for _, f := range names {
p := path.Join(dir, f)
files = append(files, strings.TrimSuffix(p, "/"))
files, err := s.getFiles(ctx, file, id)
if err != nil {
s.log.Error().Msg(err.Error())
rw.WriteHeader(http.StatusBadRequest)
return
}

dir := getDeepestCommonDir(files)

userAgent := ua.Parse(r.Header.Get("User-Agent"))

archiveName := "download"
if len(files) == 0 {
// we need to archive the whole dir
files = append(files, dir)
archiveName = path.Base(dir)
}

if userAgent.OS == ua.Windows {
archiveName += ".zip"
} else {
Expand All @@ -148,7 +184,6 @@ func (s *svc) Handler() http.Handler {
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", archiveName))
rw.Header().Set("Content-Transfer-Encoding", "binary")

var err error
if userAgent.OS == ua.Windows {
err = s.createZip(ctx, dir, files, rw)
} else {
Expand Down Expand Up @@ -202,7 +237,10 @@ func (s *svc) createTar(ctx context.Context, dir string, files []string, dst io.
return errMaxSize
}

fileName := strings.TrimPrefix(path, dir)
fileName, err := filepath.Rel(dir, path)
if err != nil {
return err
}

header := tar.Header{
Name: fileName,
Expand Down Expand Up @@ -265,7 +303,10 @@ func (s *svc) createZip(ctx context.Context, dir string, files []string, dst io.
return errMaxSize
}

fileName := strings.TrimPrefix(strings.Trim(path, dir), "/")
fileName, err := filepath.Rel(dir, path)
if err != nil {
return err
}

if fileName == "" {
return nil
Expand Down Expand Up @@ -346,3 +387,40 @@ func (s *svc) downloadFile(ctx context.Context, path string, dst io.Writer) erro
_, err = io.Copy(dst, httpRes.Body)
return err
}

func getDeepestCommonDir(files []string) string {

if len(files) == 0 {
return ""
}

// find the maximum common substring from left
res := path.Clean(files[0])

for _, file := range files[1:] {
file = path.Clean(file)

for i := 0; i < min(len(res), len(file)); i++ {
if res[i] != file[i] {
res = res[:i]
}
}

}

// the common substring could be between two / - inside a file name
for i := len(res) - 1; i >= 0; i-- {
if res[i] == '/' {
res = res[:i]
break
}
}
return res
}

func min(a, b int) int {
if a < b {
return a
}
return b
}

0 comments on commit f321bbb

Please sign in to comment.