gcompress_zip.go 8.4 KB

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