github.com/wangyougui/gf/v2@v2.6.5/encoding/gcompress/gcompress_zip.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/wangyougui/gf. 6 7 package gcompress 8 9 import ( 10 "archive/zip" 11 "bytes" 12 "context" 13 "io" 14 "os" 15 "path/filepath" 16 "strings" 17 18 "github.com/wangyougui/gf/v2/errors/gerror" 19 "github.com/wangyougui/gf/v2/internal/intlog" 20 "github.com/wangyougui/gf/v2/os/gfile" 21 "github.com/wangyougui/gf/v2/text/gstr" 22 ) 23 24 // ZipPath compresses `fileOrFolderPaths` to `dstFilePath` using zip compressing algorithm. 25 // 26 // The parameter `paths` can be either a directory or a file, which 27 // supports multiple paths join with ','. 28 // The unnecessary parameter `prefix` indicates the path prefix for zip file. 29 func ZipPath(fileOrFolderPaths, dstFilePath string, prefix ...string) error { 30 writer, err := os.Create(dstFilePath) 31 if err != nil { 32 err = gerror.Wrapf(err, `os.Create failed for name "%s"`, dstFilePath) 33 return err 34 } 35 defer writer.Close() 36 zipWriter := zip.NewWriter(writer) 37 defer zipWriter.Close() 38 for _, path := range strings.Split(fileOrFolderPaths, ",") { 39 path = strings.TrimSpace(path) 40 if err = doZipPathWriter(path, gfile.RealPath(dstFilePath), zipWriter, prefix...); err != nil { 41 return err 42 } 43 } 44 return nil 45 } 46 47 // ZipPathWriter compresses `fileOrFolderPaths` to `writer` using zip compressing algorithm. 48 // 49 // Note that the parameter `fileOrFolderPaths` can be either a directory or a file, which 50 // supports multiple paths join with ','. 51 // The unnecessary parameter `prefix` indicates the path prefix for zip file. 52 func ZipPathWriter(fileOrFolderPaths string, writer io.Writer, prefix ...string) error { 53 zipWriter := zip.NewWriter(writer) 54 defer zipWriter.Close() 55 for _, path := range strings.Split(fileOrFolderPaths, ",") { 56 path = strings.TrimSpace(path) 57 if err := doZipPathWriter(path, "", zipWriter, prefix...); err != nil { 58 return err 59 } 60 } 61 return nil 62 } 63 64 // ZipPathContent compresses `fileOrFolderPaths` to []byte using zip compressing algorithm. 65 // 66 // Note that the parameter `fileOrFolderPaths` can be either a directory or a file, which 67 // supports multiple paths join with ','. 68 // The unnecessary parameter `prefix` indicates the path prefix for zip file. 69 func ZipPathContent(fileOrFolderPaths string, prefix ...string) ([]byte, error) { 70 var ( 71 err error 72 buffer = bytes.NewBuffer(nil) 73 ) 74 if err = ZipPathWriter(fileOrFolderPaths, buffer, prefix...); err != nil { 75 return nil, err 76 } 77 return buffer.Bytes(), nil 78 } 79 80 // doZipPathWriter compresses given `fileOrFolderPaths` and writes the content to `zipWriter`. 81 // 82 // The parameter `fileOrFolderPath` can be either a single file or folder path. 83 // The parameter `exclude` specifies the exclusive file path that is not compressed to `zipWriter`, 84 // commonly the destination zip file path. 85 // The unnecessary parameter `prefix` indicates the path prefix for zip file. 86 func doZipPathWriter(fileOrFolderPath string, exclude string, zipWriter *zip.Writer, prefix ...string) error { 87 var ( 88 err error 89 files []string 90 ) 91 fileOrFolderPath, err = gfile.Search(fileOrFolderPath) 92 if err != nil { 93 return err 94 } 95 if gfile.IsDir(fileOrFolderPath) { 96 files, err = gfile.ScanDir(fileOrFolderPath, "*", true) 97 if err != nil { 98 return err 99 } 100 } else { 101 files = []string{fileOrFolderPath} 102 } 103 headerPrefix := "" 104 if len(prefix) > 0 && prefix[0] != "" { 105 headerPrefix = prefix[0] 106 } 107 headerPrefix = strings.TrimRight(headerPrefix, "\\/") 108 if gfile.IsDir(fileOrFolderPath) { 109 if len(headerPrefix) > 0 { 110 headerPrefix += "/" 111 } else { 112 headerPrefix = gfile.Basename(fileOrFolderPath) 113 } 114 } 115 headerPrefix = strings.ReplaceAll(headerPrefix, "//", "/") 116 for _, file := range files { 117 if exclude == file { 118 intlog.Printf(context.TODO(), `exclude file path: %s`, file) 119 continue 120 } 121 dir := gfile.Dir(file[len(fileOrFolderPath):]) 122 if dir == "." { 123 dir = "" 124 } 125 if err = zipFile(file, headerPrefix+dir, zipWriter); err != nil { 126 return err 127 } 128 } 129 return nil 130 } 131 132 // UnZipFile decompresses `archive` to `dstFolderPath` using zip compressing algorithm. 133 // 134 // The parameter `dstFolderPath` should be a directory. 135 // The optional parameter `zippedPrefix` specifies the unzipped path of `zippedFilePath`, 136 // which can be used to specify part of the archive file to unzip. 137 func UnZipFile(zippedFilePath, dstFolderPath string, zippedPrefix ...string) error { 138 readerCloser, err := zip.OpenReader(zippedFilePath) 139 if err != nil { 140 err = gerror.Wrapf(err, `zip.OpenReader failed for name "%s"`, dstFolderPath) 141 return err 142 } 143 defer readerCloser.Close() 144 return unZipFileWithReader(&readerCloser.Reader, dstFolderPath, zippedPrefix...) 145 } 146 147 // UnZipContent decompresses `zippedContent` to `dstFolderPath` using zip compressing algorithm. 148 // 149 // The parameter `dstFolderPath` should be a directory. 150 // The parameter `zippedPrefix` specifies the unzipped path of `zippedContent`, 151 // which can be used to specify part of the archive file to unzip. 152 func UnZipContent(zippedContent []byte, dstFolderPath string, zippedPrefix ...string) error { 153 reader, err := zip.NewReader(bytes.NewReader(zippedContent), int64(len(zippedContent))) 154 if err != nil { 155 err = gerror.Wrapf(err, `zip.NewReader failed`) 156 return err 157 } 158 return unZipFileWithReader(reader, dstFolderPath, zippedPrefix...) 159 } 160 161 func unZipFileWithReader(reader *zip.Reader, dstFolderPath string, zippedPrefix ...string) error { 162 prefix := "" 163 if len(zippedPrefix) > 0 { 164 prefix = gstr.Replace(zippedPrefix[0], `\`, `/`) 165 } 166 if err := os.MkdirAll(dstFolderPath, 0755); err != nil { 167 return err 168 } 169 var ( 170 name string 171 dstPath string 172 dstDir string 173 ) 174 for _, file := range reader.File { 175 name = gstr.Replace(file.Name, `\`, `/`) 176 name = gstr.Trim(name, "/") 177 if prefix != "" { 178 if !strings.HasPrefix(name, prefix) { 179 continue 180 } 181 name = name[len(prefix):] 182 } 183 dstPath = filepath.Join(dstFolderPath, name) 184 if file.FileInfo().IsDir() { 185 _ = os.MkdirAll(dstPath, file.Mode()) 186 continue 187 } 188 dstDir = filepath.Dir(dstPath) 189 if len(dstDir) > 0 { 190 if _, err := os.Stat(dstDir); os.IsNotExist(err) { 191 if err = os.MkdirAll(dstDir, 0755); err != nil { 192 err = gerror.Wrapf(err, `os.MkdirAll failed for path "%s"`, dstDir) 193 return err 194 } 195 } 196 } 197 fileReader, err := file.Open() 198 if err != nil { 199 err = gerror.Wrapf(err, `file.Open failed`) 200 return err 201 } 202 // The fileReader is closed in function doCopyForUnZipFileWithReader. 203 if err = doCopyForUnZipFileWithReader(file, fileReader, dstPath); err != nil { 204 return err 205 } 206 } 207 return nil 208 } 209 210 func doCopyForUnZipFileWithReader(file *zip.File, fileReader io.ReadCloser, dstPath string) error { 211 defer fileReader.Close() 212 targetFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) 213 if err != nil { 214 err = gerror.Wrapf(err, `os.OpenFile failed for name "%s"`, dstPath) 215 return err 216 } 217 defer targetFile.Close() 218 219 if _, err = io.Copy(targetFile, fileReader); err != nil { 220 err = gerror.Wrapf(err, `io.Copy failed from "%s" to "%s"`, file.Name, dstPath) 221 return err 222 } 223 return nil 224 } 225 226 // zipFile compresses the file of given `filePath` and writes the content to `zw`. 227 // The parameter `prefix` indicates the path prefix for zip file. 228 func zipFile(filePath string, prefix string, zw *zip.Writer) error { 229 file, err := os.Open(filePath) 230 if err != nil { 231 err = gerror.Wrapf(err, `os.Open failed for name "%s"`, filePath) 232 return err 233 } 234 defer file.Close() 235 236 info, err := file.Stat() 237 if err != nil { 238 err = gerror.Wrapf(err, `file.Stat failed for name "%s"`, filePath) 239 return err 240 } 241 242 header, err := createFileHeader(info, prefix) 243 if err != nil { 244 return err 245 } 246 247 if info.IsDir() { 248 header.Name += "/" 249 } else { 250 header.Method = zip.Deflate 251 } 252 253 writer, err := zw.CreateHeader(header) 254 if err != nil { 255 err = gerror.Wrapf(err, `zip.Writer.CreateHeader failed for header "%#v"`, header) 256 return err 257 } 258 if !info.IsDir() { 259 if _, err = io.Copy(writer, file); err != nil { 260 err = gerror.Wrapf(err, `io.Copy failed from "%s" to "%s"`, filePath, header.Name) 261 return err 262 } 263 } 264 return nil 265 } 266 267 func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) { 268 header, err := zip.FileInfoHeader(info) 269 if err != nil { 270 err = gerror.Wrapf(err, `zip.FileInfoHeader failed for info "%#v"`, info) 271 return nil, err 272 } 273 274 if len(prefix) > 0 { 275 prefix = strings.ReplaceAll(prefix, `\`, `/`) 276 prefix = strings.TrimRight(prefix, `/`) 277 header.Name = prefix + `/` + header.Name 278 } 279 return header, nil 280 }