github.com/gogf/gf/v2@v2.7.4/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/gogf/gf. 6 7 package gres 8 9 import ( 10 "archive/zip" 11 "io" 12 "os" 13 "strings" 14 "time" 15 16 "github.com/gogf/gf/v2/errors/gerror" 17 "github.com/gogf/gf/v2/internal/fileinfo" 18 "github.com/gogf/gf/v2/os/gfile" 19 "github.com/gogf/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 := usedOption.Prefix 66 if !(headerPrefix == "/") { 67 headerPrefix = strings.TrimRight(headerPrefix, `\/`) 68 } 69 if headerPrefix != "" && gfile.IsDir(absolutePath) { 70 headerPrefix += "/" 71 } 72 73 if headerPrefix == "" { 74 if usedOption.KeepPath { 75 // It keeps the path from file system to zip info in resource manager. 76 // Usually for relative path, it makes little sense for absolute path. 77 headerPrefix = srcPath 78 } else { 79 headerPrefix = gfile.Basename(absolutePath) 80 } 81 } 82 headerPrefix = strings.ReplaceAll(headerPrefix, `//`, `/`) 83 for _, file := range files { 84 // It here calculates the file name prefix, especially packing the directory. 85 // Eg: 86 // path: dir1 87 // file: dir1/dir2/file 88 // file[len(absolutePath):] => /dir2/file 89 // gfile.Dir(subFilePath) => /dir2 90 var subFilePath string 91 // Normal handling: remove the `absolutePath`(source directory path) for file. 92 subFilePath = file[len(absolutePath):] 93 if subFilePath != "" { 94 subFilePath = gfile.Dir(subFilePath) 95 } 96 if err = zipFile(file, headerPrefix+subFilePath, zipWriter); err != nil { 97 return err 98 } 99 } 100 // Add all directories to zip archive. 101 if headerPrefix != "" { 102 var ( 103 name string 104 tmpPath = headerPrefix 105 ) 106 for { 107 name = strings.ReplaceAll(gfile.Basename(tmpPath), `\`, `/`) 108 err = zipFileVirtual(fileinfo.New(name, 0, os.ModeDir|os.ModePerm, time.Now()), tmpPath, zipWriter) 109 if err != nil { 110 return err 111 } 112 if tmpPath == `/` || !strings.Contains(tmpPath, `/`) { 113 break 114 } 115 tmpPath = gfile.Dir(tmpPath) 116 } 117 } 118 return nil 119 } 120 121 // zipFile compresses the file of given `path` and writes the content to `zw`. 122 // The parameter `prefix` indicates the path prefix for zip file. 123 func zipFile(path string, prefix string, zw *zip.Writer) error { 124 prefix = strings.ReplaceAll(prefix, `//`, `/`) 125 file, err := os.Open(path) 126 if err != nil { 127 err = gerror.Wrapf(err, `os.Open failed for path "%s"`, path) 128 return nil 129 } 130 defer file.Close() 131 132 info, err := file.Stat() 133 if err != nil { 134 err = gerror.Wrapf(err, `read file stat failed for path "%s"`, path) 135 return err 136 } 137 138 header, err := createFileHeader(info, prefix) 139 if err != nil { 140 return err 141 } 142 if !info.IsDir() { 143 // Default compression level. 144 header.Method = zip.Deflate 145 } 146 // Zip header containing the info of a zip file. 147 writer, err := zw.CreateHeader(header) 148 if err != nil { 149 err = gerror.Wrapf(err, `create zip header failed for %#v`, header) 150 return err 151 } 152 if !info.IsDir() { 153 if _, err = io.Copy(writer, file); err != nil { 154 err = gerror.Wrapf(err, `io.Copy failed for file "%s"`, path) 155 return err 156 } 157 } 158 return nil 159 } 160 161 func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error { 162 header, err := createFileHeader(info, "") 163 if err != nil { 164 return err 165 } 166 header.Name = path 167 if _, err = zw.CreateHeader(header); err != nil { 168 err = gerror.Wrapf(err, `create zip header failed for %#v`, header) 169 return err 170 } 171 return nil 172 } 173 174 func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) { 175 header, err := zip.FileInfoHeader(info) 176 if err != nil { 177 err = gerror.Wrapf(err, `create file header failed for name "%s"`, info.Name()) 178 return nil, err 179 } 180 if len(prefix) > 0 { 181 header.Name = prefix + `/` + header.Name 182 header.Name = strings.ReplaceAll(header.Name, `\`, `/`) 183 header.Name, _ = gregex.ReplaceString(`/{2,}`, `/`, header.Name) 184 } 185 return header, nil 186 }