gitee.com/h79/goutils@v1.22.10/common/archive/zip/zip.go (about)

     1  // Package zip implements the Archive interface providing zip archiving
     2  // and compression.
     3  package zip
     4  
     5  import (
     6  	"archive/zip"
     7  	"compress/flate"
     8  	"fmt"
     9  	fileconfig "gitee.com/h79/goutils/common/file/config"
    10  	"io"
    11  	"io/fs"
    12  	"os"
    13  	"path/filepath"
    14  )
    15  
    16  // Archive zip struct.
    17  type Archive struct {
    18  	z     *zip.Writer
    19  	files map[string]bool
    20  }
    21  
    22  // New zip archive.
    23  func New(target io.Writer) Archive {
    24  	compressor := zip.NewWriter(target)
    25  	compressor.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
    26  		return flate.NewWriter(out, flate.BestCompression)
    27  	})
    28  	return Archive{
    29  		z:     compressor,
    30  		files: map[string]bool{},
    31  	}
    32  }
    33  
    34  func Copying(source *os.File, target io.Writer) (Archive, error) {
    35  	info, err := source.Stat()
    36  	if err != nil {
    37  		return Archive{}, err
    38  	}
    39  	r, err := zip.NewReader(source, info.Size())
    40  	if err != nil {
    41  		return Archive{}, err
    42  	}
    43  	w := New(target)
    44  	for _, zf := range r.File {
    45  
    46  		w.files[zf.Name] = true
    47  		hdr := zip.FileHeader{
    48  			Name:               zf.Name,
    49  			UncompressedSize64: zf.UncompressedSize64,
    50  			UncompressedSize:   zf.UncompressedSize,
    51  			CreatorVersion:     zf.CreatorVersion,
    52  			ExternalAttrs:      zf.ExternalAttrs,
    53  		}
    54  		ww, err := w.z.CreateHeader(&hdr)
    55  		if err != nil {
    56  			return Archive{}, fmt.Errorf("creating %q header in target: %w", zf.Name, err)
    57  		}
    58  		if zf.Mode().IsDir() {
    59  			continue
    60  		}
    61  		rr, err := zf.Open()
    62  		if err != nil {
    63  			return Archive{}, fmt.Errorf("opening %q from source: %w", zf.Name, err)
    64  		}
    65  		if _, err = io.Copy(ww, rr); err != nil {
    66  			_ = rr.Close()
    67  			return Archive{}, fmt.Errorf("copy from %q source to target: %w", zf.Name, err)
    68  		}
    69  		_ = rr.Close()
    70  	}
    71  	return w, nil
    72  }
    73  
    74  // Close all closeables.
    75  func (a Archive) Close() error {
    76  	if a.z != nil {
    77  		err := a.z.Close()
    78  		a.z = nil
    79  		return err
    80  	}
    81  	return nil
    82  }
    83  
    84  // Add a file to the zip archive.
    85  func (a Archive) Add(f fileconfig.File, stream ...fileconfig.ReaderStream) error {
    86  	if f.Destination == "" {
    87  		_, f.Destination = filepath.Split(f.Source)
    88  	}
    89  	if _, ok := a.files[f.Destination]; ok {
    90  		return &fs.PathError{Err: fs.ErrExist, Path: f.Destination, Op: "add"}
    91  	}
    92  	a.files[f.Destination] = true
    93  	info, err := os.Lstat(f.Source) // #nosec
    94  	if err != nil {
    95  		return err
    96  	}
    97  	header, err := zip.FileInfoHeader(info)
    98  	if err != nil {
    99  		return err
   100  	}
   101  	header.Name = f.Destination
   102  	if info.IsDir() {
   103  		header.Name += `/`
   104  	} else {
   105  		header.Method = zip.Deflate
   106  	}
   107  	if !f.Info.ParsedMTime.IsZero() {
   108  		header.Modified = f.Info.ParsedMTime
   109  	}
   110  	if f.Info.Mode != 0 {
   111  		header.SetMode(f.Info.Mode)
   112  	}
   113  	w, err := a.z.CreateHeader(header)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	if info.IsDir() {
   118  		return nil
   119  	}
   120  	if info.Mode()&os.ModeSymlink != 0 {
   121  		link, err := os.Readlink(f.Source) // #nosec
   122  		if err != nil {
   123  			return fmt.Errorf("%s: %w", f.Source, err)
   124  		}
   125  		_, err = io.WriteString(w, filepath.ToSlash(link))
   126  		return err
   127  	}
   128  	file, err := os.Open(f.Source) // #nosec
   129  	if err != nil {
   130  		return err
   131  	}
   132  	defer func(file *os.File) {
   133  		err = file.Close()
   134  		if err != nil {
   135  		}
   136  	}(file)
   137  	if _, err = io.Copy(w, file); err != nil {
   138  		return err
   139  	}
   140  	for i := range stream {
   141  		stream[i].OnReader(file)
   142  	}
   143  	return nil
   144  }
   145  
   146  // TODO: test fileinfo stuff