You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
5.9 KiB

  1. // Copyright 2013 Unknown
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. // Package zip enables you to transparently read or write ZIP compressed archives and the files inside them.
  15. package zip
  16. import (
  17. "archive/zip"
  18. "errors"
  19. "io"
  20. "os"
  21. "path"
  22. "strings"
  23. "github.com/Unknwon/cae"
  24. )
  25. // A File represents a file or directory entry in archive.
  26. type File struct {
  27. *zip.FileHeader
  28. oldName string // NOTE: unused, for future change name feature.
  29. oldComment string // NOTE: unused, for future change comment feature.
  30. absPath string // Absolute path of local file system.
  31. tmpPath string
  32. }
  33. // A ZipArchive represents a file archive, compressed with Zip.
  34. type ZipArchive struct {
  35. *zip.ReadCloser
  36. FileName string
  37. Comment string
  38. NumFiles int
  39. Flag int
  40. Permission os.FileMode
  41. files []*File
  42. isHasChanged bool
  43. // For supporting flushing to io.Writer.
  44. writer io.Writer
  45. isHasWriter bool
  46. }
  47. // OpenFile is the generalized open call; most users will use Open
  48. // instead. It opens the named zip file with specified flag
  49. // (O_RDONLY etc.) if applicable. If successful,
  50. // methods on the returned ZipArchive can be used for I/O.
  51. // If there is an error, it will be of type *PathError.
  52. func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) {
  53. z := new(ZipArchive)
  54. err := z.Open(name, flag, perm)
  55. return z, err
  56. }
  57. // Create creates the named zip file, truncating
  58. // it if it already exists. If successful, methods on the returned
  59. // ZipArchive can be used for I/O; the associated file descriptor has mode
  60. // O_RDWR.
  61. // If there is an error, it will be of type *PathError.
  62. func Create(name string) (*ZipArchive, error) {
  63. os.MkdirAll(path.Dir(name), os.ModePerm)
  64. return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
  65. }
  66. // Open opens the named zip file for reading. If successful, methods on
  67. // the returned ZipArchive can be used for reading; the associated file
  68. // descriptor has mode O_RDONLY.
  69. // If there is an error, it will be of type *PathError.
  70. func Open(name string) (*ZipArchive, error) {
  71. return OpenFile(name, os.O_RDONLY, 0)
  72. }
  73. // New accepts a variable that implemented interface io.Writer
  74. // for write-only purpose operations.
  75. func New(w io.Writer) *ZipArchive {
  76. return &ZipArchive{
  77. writer: w,
  78. isHasWriter: true,
  79. }
  80. }
  81. // List returns a string slice of files' name in ZipArchive.
  82. // Specify prefixes will be used as filters.
  83. func (z *ZipArchive) List(prefixes ...string) []string {
  84. isHasPrefix := len(prefixes) > 0
  85. names := make([]string, 0, z.NumFiles)
  86. for _, f := range z.files {
  87. if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) {
  88. continue
  89. }
  90. names = append(names, f.Name)
  91. }
  92. return names
  93. }
  94. // AddEmptyDir adds a raw directory entry to ZipArchive,
  95. // it returns false if same directory enry already existed.
  96. func (z *ZipArchive) AddEmptyDir(dirPath string) bool {
  97. dirPath = strings.Replace(dirPath, "\\", "/", -1)
  98. if !strings.HasSuffix(dirPath, "/") {
  99. dirPath += "/"
  100. }
  101. for _, f := range z.files {
  102. if dirPath == f.Name {
  103. return false
  104. }
  105. }
  106. dirPath = strings.TrimSuffix(dirPath, "/")
  107. if strings.Contains(dirPath, "/") {
  108. // Auto add all upper level directories.
  109. z.AddEmptyDir(path.Dir(dirPath))
  110. }
  111. z.files = append(z.files, &File{
  112. FileHeader: &zip.FileHeader{
  113. Name: dirPath + "/",
  114. UncompressedSize: 0,
  115. },
  116. })
  117. z.updateStat()
  118. return true
  119. }
  120. // AddDir adds a directory and subdirectories entries to ZipArchive.
  121. func (z *ZipArchive) AddDir(dirPath, absPath string) error {
  122. dir, err := os.Open(absPath)
  123. if err != nil {
  124. return err
  125. }
  126. defer dir.Close()
  127. // Make sure we have all upper level directories.
  128. z.AddEmptyDir(dirPath)
  129. fis, err := dir.Readdir(0)
  130. if err != nil {
  131. return err
  132. }
  133. for _, fi := range fis {
  134. curPath := absPath + "/" + fi.Name()
  135. tmpRecPath := path.Join(dirPath, fi.Name())
  136. if fi.IsDir() {
  137. if err = z.AddDir(tmpRecPath, curPath); err != nil {
  138. return err
  139. }
  140. } else {
  141. if err = z.AddFile(tmpRecPath, curPath); err != nil {
  142. return err
  143. }
  144. }
  145. }
  146. return nil
  147. }
  148. // updateStat should be called after every change for rebuilding statistic.
  149. func (z *ZipArchive) updateStat() {
  150. z.NumFiles = len(z.files)
  151. z.isHasChanged = true
  152. }
  153. // AddFile adds a file entry to ZipArchive.
  154. func (z *ZipArchive) AddFile(fileName, absPath string) error {
  155. fileName = strings.Replace(fileName, "\\", "/", -1)
  156. absPath = strings.Replace(absPath, "\\", "/", -1)
  157. if cae.IsFilter(absPath) {
  158. return nil
  159. }
  160. f, err := os.Open(absPath)
  161. if err != nil {
  162. return err
  163. }
  164. defer f.Close()
  165. fi, err := f.Stat()
  166. if err != nil {
  167. return err
  168. }
  169. file := new(File)
  170. file.FileHeader, err = zip.FileInfoHeader(fi)
  171. if err != nil {
  172. return err
  173. }
  174. file.Name = fileName
  175. file.absPath = absPath
  176. z.AddEmptyDir(path.Dir(fileName))
  177. isExist := false
  178. for _, f := range z.files {
  179. if fileName == f.Name {
  180. f = file
  181. isExist = true
  182. break
  183. }
  184. }
  185. if !isExist {
  186. z.files = append(z.files, file)
  187. }
  188. z.updateStat()
  189. return nil
  190. }
  191. // DeleteIndex deletes an entry in the archive by its index.
  192. func (z *ZipArchive) DeleteIndex(idx int) error {
  193. if idx >= z.NumFiles {
  194. return errors.New("index out of range of number of files")
  195. }
  196. z.files = append(z.files[:idx], z.files[idx+1:]...)
  197. return nil
  198. }
  199. // DeleteName deletes an entry in the archive by its name.
  200. func (z *ZipArchive) DeleteName(name string) error {
  201. for i, f := range z.files {
  202. if f.Name == name {
  203. return z.DeleteIndex(i)
  204. }
  205. }
  206. return errors.New("entry with given name not found")
  207. }