github.com/wangyougui/gf/v2@v2.6.5/os/gres/gres_func_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 gres 8 9 import ( 10 "archive/zip" 11 "io" 12 "os" 13 "strings" 14 "time" 15 16 "github.com/wangyougui/gf/v2/errors/gerror" 17 "github.com/wangyougui/gf/v2/internal/fileinfo" 18 "github.com/wangyougui/gf/v2/os/gfile" 19 "github.com/wangyougui/gf/v2/text/gregex" 20 ) 21 22 // ZipPathWriter compresses `paths` to `writer` using zip compressing algorithm. 23 // The unnecessary parameter `prefix` indicates the path prefix for zip file. 24 // 25 // Note that the parameter `paths` can be either a directory or a file, which 26 // supports multiple paths join with ','. 27 func zipPathWriter(paths string, writer io.Writer, option ...Option) error { 28 zipWriter := zip.NewWriter(writer) 29 defer zipWriter.Close() 30 for _, path := range strings.Split(paths, ",") { 31 path = strings.TrimSpace(path) 32 if err := doZipPathWriter(path, zipWriter, option...); err != nil { 33 return err 34 } 35 } 36 return nil 37 } 38 39 // doZipPathWriter compresses the file of given `path` and writes the content to `zipWriter`. 40 // The parameter `exclude` specifies the exclusive file path that is not compressed to `zipWriter`, 41 // commonly the destination zip file path. 42 // The unnecessary parameter `prefix` indicates the path prefix for zip file. 43 func doZipPathWriter(srcPath string, zipWriter *zip.Writer, option ...Option) error { 44 var ( 45 err error 46 files []string 47 usedOption Option 48 absolutePath string 49 ) 50 if len(option) > 0 { 51 usedOption = option[0] 52 } 53 absolutePath, err = gfile.Search(srcPath) 54 if err != nil { 55 return err 56 } 57 if gfile.IsDir(absolutePath) { 58 files, err = gfile.ScanDir(absolutePath, "*", true) 59 if err != nil { 60 return err 61 } 62 } else { 63 files = []string{absolutePath} 64 } 65 headerPrefix := strings.TrimRight(usedOption.Prefix, `\/`) 66 if headerPrefix != "" && gfile.IsDir(absolutePath) { 67 headerPrefix += "/" 68 } 69 70 if headerPrefix == "" { 71 if usedOption.KeepPath { 72 // It keeps the path from file system to zip info in resource manager. 73 // Usually for relative path, it makes little sense for absolute path. 74 headerPrefix = srcPath 75 } else { 76 headerPrefix = gfile.Basename(absolutePath) 77 } 78 } 79 headerPrefix = strings.ReplaceAll(headerPrefix, `//`, `/`) 80 for _, file := range files { 81 // It here calculates the file name prefix, especially packing the directory. 82 // Eg: 83 // path: dir1 84 // file: dir1/dir2/file 85 // file[len(absolutePath):] => /dir2/file 86 // gfile.Dir(subFilePath) => /dir2 87 var subFilePath string 88 // Normal handling: remove the `absolutePath`(source directory path) for file. 89 subFilePath = file[len(absolutePath):] 90 if subFilePath != "" { 91 subFilePath = gfile.Dir(subFilePath) 92 } 93 if err = zipFile(file, headerPrefix+subFilePath, zipWriter); err != nil { 94 return err 95 } 96 } 97 // Add all directories to zip archive. 98 if headerPrefix != "" { 99 var ( 100 name string 101 tmpPath = headerPrefix 102 ) 103 for { 104 name = strings.ReplaceAll(gfile.Basename(tmpPath), `\`, `/`) 105 err = zipFileVirtual(fileinfo.New(name, 0, os.ModeDir|os.ModePerm, time.Now()), tmpPath, zipWriter) 106 if err != nil { 107 return err 108 } 109 if tmpPath == `/` || !strings.Contains(tmpPath, `/`) { 110 break 111 } 112 tmpPath = gfile.Dir(tmpPath) 113 } 114 } 115 return nil 116 } 117 118 // zipFile compresses the file of given `path` and writes the content to `zw`. 119 // The parameter `prefix` indicates the path prefix for zip file. 120 func zipFile(path string, prefix string, zw *zip.Writer) error { 121 prefix = strings.ReplaceAll(prefix, `//`, `/`) 122 file, err := os.Open(path) 123 if err != nil { 124 err = gerror.Wrapf(err, `os.Open failed for path "%s"`, path) 125 return nil 126 } 127 defer file.Close() 128 129 info, err := file.Stat() 130 if err != nil { 131 err = gerror.Wrapf(err, `read file stat failed for path "%s"`, path) 132 return err 133 } 134 135 header, err := createFileHeader(info, prefix) 136 if err != nil { 137 return err 138 } 139 if !info.IsDir() { 140 // Default compression level. 141 header.Method = zip.Deflate 142 } 143 // Zip header containing the info of a zip file. 144 writer, err := zw.CreateHeader(header) 145 if err != nil { 146 err = gerror.Wrapf(err, `create zip header failed for %#v`, header) 147 return err 148 } 149 if !info.IsDir() { 150 if _, err = io.Copy(writer, file); err != nil { 151 err = gerror.Wrapf(err, `io.Copy failed for file "%s"`, path) 152 return err 153 } 154 } 155 return nil 156 } 157 158 func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error { 159 header, err := createFileHeader(info, "") 160 if err != nil { 161 return err 162 } 163 header.Name = path 164 if _, err = zw.CreateHeader(header); err != nil { 165 err = gerror.Wrapf(err, `create zip header failed for %#v`, header) 166 return err 167 } 168 return nil 169 } 170 171 func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) { 172 header, err := zip.FileInfoHeader(info) 173 if err != nil { 174 err = gerror.Wrapf(err, `create file header failed for name "%s"`, info.Name()) 175 return nil, err 176 } 177 if len(prefix) > 0 { 178 header.Name = prefix + `/` + header.Name 179 header.Name = strings.ReplaceAll(header.Name, `\`, `/`) 180 header.Name, _ = gregex.ReplaceString(`/{2,}`, `/`, header.Name) 181 } 182 return header, nil 183 }