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