write.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. package zip
  2. import (
  3. "archive/zip"
  4. "fmt"
  5. "io"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "strings"
  10. "gitlab.com/yoginth/cae"
  11. )
  12. // Switcher of printing trace information when pack and extract.
  13. var Verbose = true
  14. // extractFile extracts zip.File to file system.
  15. func extractFile(f *zip.File, destPath string) error {
  16. filePath := path.Join(destPath, f.Name)
  17. os.MkdirAll(path.Dir(filePath), os.ModePerm)
  18. rc, err := f.Open()
  19. if err != nil {
  20. return err
  21. }
  22. defer rc.Close()
  23. fw, err := os.Create(filePath)
  24. if err != nil {
  25. return err
  26. }
  27. defer fw.Close()
  28. if _, err = io.Copy(fw, rc); err != nil {
  29. return err
  30. }
  31. // Skip symbolic links.
  32. if f.FileInfo().Mode()&os.ModeSymlink != 0 {
  33. return nil
  34. }
  35. // Set back file information.
  36. if err = os.Chtimes(filePath, f.ModTime(), f.ModTime()); err != nil {
  37. return err
  38. }
  39. return os.Chmod(filePath, f.FileInfo().Mode())
  40. }
  41. var defaultExtractFunc = func(fullName string, fi os.FileInfo) error {
  42. if !Verbose {
  43. return nil
  44. }
  45. fmt.Println("Extracting file..." + fullName)
  46. return nil
  47. }
  48. // ExtractToFunc extracts the whole archive or the given files to the
  49. // specified destination.
  50. // It accepts a function as a middleware for custom operations.
  51. func (z *ZipArchive) ExtractToFunc(destPath string, fn cae.HookFunc, entries ...string) (err error) {
  52. destPath = strings.Replace(destPath, "\\", "/", -1)
  53. isHasEntry := len(entries) > 0
  54. if Verbose {
  55. fmt.Println("Unzipping " + z.FileName + "...")
  56. }
  57. os.MkdirAll(destPath, os.ModePerm)
  58. for _, f := range z.File {
  59. f.Name = strings.Replace(f.Name, "\\", "/", -1)
  60. // Directory.
  61. if strings.HasSuffix(f.Name, "/") {
  62. if isHasEntry {
  63. if cae.IsEntry(f.Name, entries) {
  64. if err = fn(f.Name, f.FileInfo()); err != nil {
  65. continue
  66. }
  67. os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm)
  68. }
  69. continue
  70. }
  71. if err = fn(f.Name, f.FileInfo()); err != nil {
  72. continue
  73. }
  74. os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm)
  75. continue
  76. }
  77. // File.
  78. if isHasEntry {
  79. if cae.IsEntry(f.Name, entries) {
  80. if err = fn(f.Name, f.FileInfo()); err != nil {
  81. continue
  82. }
  83. err = extractFile(f, destPath)
  84. }
  85. } else {
  86. if err = fn(f.Name, f.FileInfo()); err != nil {
  87. continue
  88. }
  89. err = extractFile(f, destPath)
  90. }
  91. if err != nil {
  92. return err
  93. }
  94. }
  95. return nil
  96. }
  97. // ExtractToFunc extracts the whole archive or the given files to the
  98. // specified destination.
  99. // It accepts a function as a middleware for custom operations.
  100. func ExtractToFunc(srcPath, destPath string, fn cae.HookFunc, entries ...string) (err error) {
  101. z, err := Open(srcPath)
  102. if err != nil {
  103. return err
  104. }
  105. defer z.Close()
  106. return z.ExtractToFunc(destPath, fn, entries...)
  107. }
  108. // ExtractTo extracts the whole archive or the given files to the
  109. // specified destination.
  110. // Call Flush() to apply changes before this.
  111. func (z *ZipArchive) ExtractTo(destPath string, entries ...string) (err error) {
  112. return z.ExtractToFunc(destPath, defaultExtractFunc, entries...)
  113. }
  114. // ExtractTo extracts given archive or the given files to the
  115. // specified destination.
  116. func ExtractTo(srcPath, destPath string, entries ...string) (err error) {
  117. return ExtractToFunc(srcPath, destPath, defaultExtractFunc, entries...)
  118. }
  119. // extractFile extracts file from ZipArchive to file system.
  120. func (z *ZipArchive) extractFile(f *File) error {
  121. if !z.isHasWriter {
  122. for _, zf := range z.ReadCloser.File {
  123. if f.Name == zf.Name {
  124. return extractFile(zf, path.Dir(f.tmpPath))
  125. }
  126. }
  127. }
  128. return cae.Copy(f.tmpPath, f.absPath)
  129. }
  130. // Flush saves changes to original zip file if any.
  131. func (z *ZipArchive) Flush() error {
  132. if !z.isHasChanged || (z.ReadCloser == nil && !z.isHasWriter) {
  133. return nil
  134. }
  135. // Extract to tmp path and pack back.
  136. tmpPath := path.Join(os.TempDir(), "cae", path.Base(z.FileName))
  137. os.RemoveAll(tmpPath)
  138. defer os.RemoveAll(tmpPath)
  139. for _, f := range z.files {
  140. if strings.HasSuffix(f.Name, "/") {
  141. os.MkdirAll(path.Join(tmpPath, f.Name), os.ModePerm)
  142. continue
  143. }
  144. // Relative path inside zip temporary changed.
  145. f.tmpPath = path.Join(tmpPath, f.Name)
  146. if err := z.extractFile(f); err != nil {
  147. return err
  148. }
  149. }
  150. if z.isHasWriter {
  151. return packToWriter(tmpPath, z.writer, defaultPackFunc, true)
  152. }
  153. if err := PackTo(tmpPath, z.FileName); err != nil {
  154. return err
  155. }
  156. return z.Open(z.FileName, os.O_RDWR|os.O_TRUNC, z.Permission)
  157. }
  158. // packFile packs a file or directory to zip.Writer.
  159. func packFile(srcFile string, recPath string, zw *zip.Writer, fi os.FileInfo) error {
  160. if fi.IsDir() {
  161. fh, err := zip.FileInfoHeader(fi)
  162. if err != nil {
  163. return err
  164. }
  165. fh.Name = recPath + "/"
  166. if _, err = zw.CreateHeader(fh); err != nil {
  167. return err
  168. }
  169. } else {
  170. fh, err := zip.FileInfoHeader(fi)
  171. if err != nil {
  172. return err
  173. }
  174. fh.Name = recPath
  175. fh.Method = zip.Deflate
  176. fw, err := zw.CreateHeader(fh)
  177. if err != nil {
  178. return err
  179. }
  180. if fi.Mode()&os.ModeSymlink != 0 {
  181. target, err := os.Readlink(srcFile)
  182. if err != nil {
  183. return err
  184. }
  185. if _, err = fw.Write([]byte(target)); err != nil {
  186. return err
  187. }
  188. } else {
  189. f, err := os.Open(srcFile)
  190. if err != nil {
  191. return err
  192. }
  193. defer f.Close()
  194. if _, err = io.Copy(fw, f); err != nil {
  195. return err
  196. }
  197. }
  198. }
  199. return nil
  200. }
  201. // packDir packs a directory and its subdirectories and files
  202. // recursively to zip.Writer.
  203. func packDir(srcPath string, recPath string, zw *zip.Writer, fn cae.HookFunc) error {
  204. dir, err := os.Open(srcPath)
  205. if err != nil {
  206. return err
  207. }
  208. defer dir.Close()
  209. fis, err := dir.Readdir(0)
  210. if err != nil {
  211. return err
  212. }
  213. for _, fi := range fis {
  214. if cae.IsFilter(fi.Name()) {
  215. continue
  216. }
  217. curPath := srcPath + "/" + fi.Name()
  218. tmpRecPath := filepath.Join(recPath, fi.Name())
  219. if err = fn(curPath, fi); err != nil {
  220. continue
  221. }
  222. if fi.IsDir() {
  223. if err = packFile(srcPath, tmpRecPath, zw, fi); err != nil {
  224. return err
  225. }
  226. err = packDir(curPath, tmpRecPath, zw, fn)
  227. } else {
  228. err = packFile(curPath, tmpRecPath, zw, fi)
  229. }
  230. if err != nil {
  231. return err
  232. }
  233. }
  234. return nil
  235. }
  236. // packToWriter packs given path object to io.Writer.
  237. func packToWriter(srcPath string, w io.Writer, fn func(fullName string, fi os.FileInfo) error, includeDir bool) error {
  238. zw := zip.NewWriter(w)
  239. defer zw.Close()
  240. f, err := os.Open(srcPath)
  241. if err != nil {
  242. return err
  243. }
  244. defer f.Close()
  245. fi, err := f.Stat()
  246. if err != nil {
  247. return err
  248. }
  249. basePath := path.Base(srcPath)
  250. if fi.IsDir() {
  251. if includeDir {
  252. if err = packFile(srcPath, basePath, zw, fi); err != nil {
  253. return err
  254. }
  255. } else {
  256. basePath = ""
  257. }
  258. return packDir(srcPath, basePath, zw, fn)
  259. }
  260. return packFile(srcPath, basePath, zw, fi)
  261. }
  262. // packTo packs given source path object to target path.
  263. func packTo(srcPath, destPath string, fn cae.HookFunc, includeDir bool) error {
  264. fw, err := os.Create(destPath)
  265. if err != nil {
  266. return err
  267. }
  268. defer fw.Close()
  269. return packToWriter(srcPath, fw, fn, includeDir)
  270. }
  271. // PackToFunc packs the complete archive to the specified destination.
  272. // It accepts a function as a middleware for custom operations.
  273. func PackToFunc(srcPath, destPath string, fn func(fullName string, fi os.FileInfo) error, includeDir ...bool) error {
  274. isIncludeDir := false
  275. if len(includeDir) > 0 && includeDir[0] {
  276. isIncludeDir = true
  277. }
  278. return packTo(srcPath, destPath, fn, isIncludeDir)
  279. }
  280. var defaultPackFunc = func(fullName string, fi os.FileInfo) error {
  281. if !Verbose {
  282. return nil
  283. }
  284. if fi.IsDir() {
  285. fmt.Printf("Adding dir...%s\n", fullName)
  286. } else {
  287. fmt.Printf("Adding file...%s\n", fullName)
  288. }
  289. return nil
  290. }
  291. // PackTo packs the whole archive to the specified destination.
  292. // Call Flush() will automatically call this in the end.
  293. func PackTo(srcPath, destPath string, includeDir ...bool) error {
  294. return PackToFunc(srcPath, destPath, defaultPackFunc, includeDir...)
  295. }
  296. // Close opens or creates archive and save changes.
  297. func (z *ZipArchive) Close() (err error) {
  298. if err = z.Flush(); err != nil {
  299. return err
  300. }
  301. if z.ReadCloser != nil {
  302. if err = z.ReadCloser.Close(); err != nil {
  303. return err
  304. }
  305. z.ReadCloser = nil
  306. }
  307. return nil
  308. }