github.com/cs3org/reva/v2@v2.27.7/internal/http/services/archiver/manager/archiver.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package manager
    20  
    21  import (
    22  	"archive/tar"
    23  	"archive/zip"
    24  	"context"
    25  	"io"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    30  	"github.com/cs3org/reva/v2/pkg/storage/utils/downloader"
    31  	"github.com/cs3org/reva/v2/pkg/storage/utils/walker"
    32  	"github.com/cs3org/reva/v2/pkg/utils"
    33  )
    34  
    35  // Config is the config for the Archiver
    36  type Config struct {
    37  	MaxNumFiles int64
    38  	MaxSize     int64
    39  }
    40  
    41  // Archiver is the struct able to create an archive
    42  type Archiver struct {
    43  	resources  []*provider.ResourceId
    44  	walker     walker.Walker
    45  	downloader downloader.Downloader
    46  	config     Config
    47  }
    48  
    49  // NewArchiver creates a new archiver able to create an archive containing the files in the list
    50  func NewArchiver(r []*provider.ResourceId, w walker.Walker, d downloader.Downloader, config Config) (*Archiver, error) {
    51  	if len(r) == 0 {
    52  		return nil, ErrEmptyList{}
    53  	}
    54  
    55  	arc := &Archiver{
    56  		resources:  r,
    57  		walker:     w,
    58  		downloader: d,
    59  		config:     config,
    60  	}
    61  	return arc, nil
    62  }
    63  
    64  // CreateTar creates a tar and write it into the dst Writer
    65  func (a *Archiver) CreateTar(ctx context.Context, dst io.Writer) (func(), error) {
    66  	w := tar.NewWriter(dst)
    67  	closer := func() {
    68  		_ = w.Close()
    69  	}
    70  
    71  	var filesCount, sizeFiles int64
    72  
    73  	for _, root := range a.resources {
    74  
    75  		err := a.walker.Walk(ctx, root, func(wd string, info *provider.ResourceInfo, err error) error {
    76  			if err != nil {
    77  				return err
    78  			}
    79  
    80  			// when archiving a space we can omit the spaceroot
    81  			if utils.IsSpaceRoot(info) {
    82  				return nil
    83  			}
    84  
    85  			isDir := info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER
    86  
    87  			filesCount++
    88  			if filesCount > a.config.MaxNumFiles {
    89  				return ErrMaxFileCount{}
    90  			}
    91  
    92  			if !isDir {
    93  				// only add the size if the resource is not a directory
    94  				// as its size could be resursive-computed, and we would
    95  				// count the files not only once
    96  				sizeFiles += int64(info.Size)
    97  				if sizeFiles > a.config.MaxSize {
    98  					return ErrMaxSize{}
    99  				}
   100  			}
   101  
   102  			header := tar.Header{
   103  				Name:    filepath.Join(wd, info.Path),
   104  				ModTime: time.Unix(int64(info.Mtime.Seconds), 0),
   105  			}
   106  
   107  			if isDir {
   108  				// the resource is a folder
   109  				header.Mode = 0755
   110  				header.Typeflag = tar.TypeDir
   111  			} else {
   112  				header.Mode = 0644
   113  				header.Typeflag = tar.TypeReg
   114  				header.Size = int64(info.Size)
   115  			}
   116  
   117  			err = w.WriteHeader(&header)
   118  			if err != nil {
   119  				return err
   120  			}
   121  
   122  			if !isDir {
   123  				err = a.downloader.Download(ctx, info.Id, w)
   124  				if err != nil {
   125  					return err
   126  				}
   127  			}
   128  			return nil
   129  		})
   130  
   131  		if err != nil {
   132  			return closer, err
   133  		}
   134  
   135  	}
   136  	return closer, nil
   137  }
   138  
   139  // CreateZip creates a zip and write it into the dst Writer
   140  func (a *Archiver) CreateZip(ctx context.Context, dst io.Writer) (func(), error) {
   141  	w := zip.NewWriter(dst)
   142  	closer := func() {
   143  		_ = w.Close()
   144  	}
   145  
   146  	var filesCount, sizeFiles int64
   147  
   148  	for _, root := range a.resources {
   149  
   150  		err := a.walker.Walk(ctx, root, func(wd string, info *provider.ResourceInfo, err error) error {
   151  			if err != nil {
   152  				return err
   153  			}
   154  
   155  			// when archiving a space we can omit the spaceroot
   156  			if utils.IsSpaceRoot(info) {
   157  				return nil
   158  			}
   159  
   160  			isDir := info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER
   161  
   162  			filesCount++
   163  			if filesCount > a.config.MaxNumFiles {
   164  				return ErrMaxFileCount{}
   165  			}
   166  
   167  			if !isDir {
   168  				// only add the size if the resource is not a directory
   169  				// as its size could be resursive-computed, and we would
   170  				// count the files not only once
   171  				sizeFiles += int64(info.Size)
   172  				if sizeFiles > a.config.MaxSize {
   173  					return ErrMaxSize{}
   174  				}
   175  			}
   176  
   177  			header := zip.FileHeader{
   178  				Name:     filepath.Join(wd, info.Path),
   179  				Modified: time.Unix(int64(info.Mtime.Seconds), 0),
   180  			}
   181  
   182  			if isDir {
   183  				header.Name += "/"
   184  			} else {
   185  				header.UncompressedSize64 = info.Size
   186  			}
   187  
   188  			dst, err := w.CreateHeader(&header)
   189  			if err != nil {
   190  				return err
   191  			}
   192  
   193  			if !isDir {
   194  				err = a.downloader.Download(ctx, info.Id, dst)
   195  				if err != nil {
   196  					return err
   197  				}
   198  			}
   199  			return nil
   200  		})
   201  
   202  		if err != nil {
   203  			return closer, err
   204  		}
   205  
   206  	}
   207  	return closer, nil
   208  }