zipfiles.nim 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements a zip archive creator/reader/modifier.
  10. import
  11. streams, libzip, times, os, strutils
  12. const BufSize = 8 * 1024
  13. type
  14. ZipArchive* = object of RootObj ## represents a zip archive
  15. mode: FileMode
  16. w: PZip
  17. {.deprecated: [TZipArchive: ZipArchive].}
  18. proc zipError(z: var ZipArchive) =
  19. var e: ref IOError
  20. new(e)
  21. e.msg = $zip_strerror(z.w)
  22. raise e
  23. proc open*(z: var ZipArchive, filename: string, mode: FileMode = fmRead): bool =
  24. ## Opens a zip file for reading, writing or appending. All file modes are
  25. ## supported. Returns true iff successful, false otherwise.
  26. var err, flags: int32
  27. case mode
  28. of fmRead, fmReadWriteExisting, fmAppend: flags = 0
  29. of fmWrite:
  30. if existsFile(filename): removeFile(filename)
  31. flags = ZIP_CREATE or ZIP_EXCL
  32. of fmReadWrite: flags = ZIP_CREATE
  33. z.w = zip_open(filename, flags, addr(err))
  34. z.mode = mode
  35. result = z.w != nil
  36. proc close*(z: var ZipArchive) =
  37. ## Closes a zip file.
  38. zip_close(z.w)
  39. proc createDir*(z: var ZipArchive, dir: string) =
  40. ## Creates a directory within the `z` archive. This does not fail if the
  41. ## directory already exists. Note that for adding a file like
  42. ## ``"path1/path2/filename"`` it is not necessary
  43. ## to create the ``"path/path2"`` subdirectories - it will be done
  44. ## automatically by ``addFile``.
  45. assert(z.mode != fmRead)
  46. discard zip_add_dir(z.w, dir)
  47. zip_error_clear(z.w)
  48. proc addFile*(z: var ZipArchive, dest, src: string) =
  49. ## Adds the file `src` to the archive `z` with the name `dest`. `dest`
  50. ## may contain a path that will be created.
  51. assert(z.mode != fmRead)
  52. if not fileExists(src):
  53. raise newException(IOError, "File '" & src & "' does not exist")
  54. var zipsrc = zip_source_file(z.w, src, 0, -1)
  55. if zipsrc == nil:
  56. #echo("Dest: " & dest)
  57. #echo("Src: " & src)
  58. zipError(z)
  59. if zip_add(z.w, dest, zipsrc) < 0'i32:
  60. zip_source_free(zipsrc)
  61. zipError(z)
  62. proc addFile*(z: var ZipArchive, file: string) =
  63. ## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is
  64. ## the name of the destination.
  65. addFile(z, file, file)
  66. proc mySourceCallback(state, data: pointer, len: int,
  67. cmd: ZipSourceCmd): int {.cdecl.} =
  68. var src = cast[Stream](state)
  69. case cmd
  70. of ZIP_SOURCE_OPEN:
  71. if src.setPositionImpl != nil: setPosition(src, 0) # reset
  72. of ZIP_SOURCE_READ:
  73. result = readData(src, data, len)
  74. of ZIP_SOURCE_CLOSE: close(src)
  75. of ZIP_SOURCE_STAT:
  76. var stat = cast[PZipStat](data)
  77. zip_stat_init(stat)
  78. stat.size = high(int32)-1 # we don't know the size
  79. stat.mtime = getTime()
  80. result = sizeof(ZipStat)
  81. of ZIP_SOURCE_ERROR:
  82. var err = cast[ptr array[0..1, cint]](data)
  83. err[0] = ZIP_ER_INTERNAL
  84. err[1] = 0
  85. result = 2*sizeof(cint)
  86. of constZIP_SOURCE_FREE: GC_unref(src)
  87. of ZIP_SOURCE_SUPPORTS:
  88. # By default a read-only source is supported, which suits us.
  89. result = -1
  90. else:
  91. # An unknown command, failing
  92. result = -1
  93. proc addFile*(z: var ZipArchive, dest: string, src: Stream) =
  94. ## Adds a file named with `dest` to the archive `z`. `dest`
  95. ## may contain a path. The file's content is read from the `src` stream.
  96. assert(z.mode != fmRead)
  97. GC_ref(src)
  98. var zipsrc = zip_source_function(z.w, mySourceCallback, cast[pointer](src))
  99. if zipsrc == nil: zipError(z)
  100. if zip_add(z.w, dest, zipsrc) < 0'i32:
  101. zip_source_free(zipsrc)
  102. zipError(z)
  103. # -------------- zip file stream ---------------------------------------------
  104. type
  105. TZipFileStream = object of StreamObj
  106. f: PZipFile
  107. atEnd: bool
  108. PZipFileStream* =
  109. ref TZipFileStream ## a reader stream of a file within a zip archive
  110. proc fsClose(s: Stream) = zip_fclose(PZipFileStream(s).f)
  111. proc fsAtEnd(s: Stream): bool = PZipFileStream(s).atEnd
  112. proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
  113. result = zip_fread(PZipFileStream(s).f, buffer, bufLen)
  114. if result == 0:
  115. PZipFileStream(s).atEnd = true
  116. proc newZipFileStream(f: PZipFile): PZipFileStream =
  117. new(result)
  118. result.f = f
  119. result.atEnd = false
  120. result.closeImpl = fsClose
  121. result.readDataImpl = fsReadData
  122. result.atEndImpl = fsAtEnd
  123. # other methods are nil!
  124. # ----------------------------------------------------------------------------
  125. proc getStream*(z: var ZipArchive, filename: string): PZipFileStream =
  126. ## returns a stream that can be used to read the file named `filename`
  127. ## from the archive `z`. Returns nil in case of an error.
  128. ## The returned stream does not support the `setPosition`, `getPosition`,
  129. ## `writeData` or `atEnd` methods.
  130. var x = zip_fopen(z.w, filename, 0'i32)
  131. if x != nil: result = newZipFileStream(x)
  132. iterator walkFiles*(z: var ZipArchive): string =
  133. ## walks over all files in the archive `z` and returns the filename
  134. ## (including the path).
  135. var i = 0'i32
  136. var num = zip_get_num_files(z.w)
  137. while i < num:
  138. yield $zip_get_name(z.w, i, 0'i32)
  139. inc(i)
  140. proc extractFile*(z: var ZipArchive, srcFile: string, dest: Stream) =
  141. ## extracts a file from the zip archive `z` to the destination stream.
  142. var buf: array[BufSize, byte]
  143. var strm = getStream(z, srcFile)
  144. while true:
  145. let bytesRead = strm.readData(addr(buf[0]), buf.len)
  146. if bytesRead <= 0: break
  147. dest.writeData(addr(buf[0]), bytesRead)
  148. dest.flush()
  149. strm.close()
  150. proc extractFile*(z: var ZipArchive, srcFile: string, dest: string) =
  151. ## extracts a file from the zip archive `z` to the destination filename.
  152. var file = newFileStream(dest, fmWrite)
  153. if file.isNil:
  154. raise newException(IOError, "Failed to create output file: " & dest)
  155. extractFile(z, srcFile, file)
  156. file.close()
  157. proc extractAll*(z: var ZipArchive, dest: string) =
  158. ## extracts all files from archive `z` to the destination directory.
  159. createDir(dest)
  160. for file in walkFiles(z):
  161. if file.contains("/"):
  162. createDir(dest / file[0..file.rfind("/")])
  163. extractFile(z, file, dest / file)
  164. when not defined(testing) and true:
  165. var zip: ZipArchive
  166. if not zip.open("nim-0.11.0.zip"):
  167. raise newException(IOError, "opening zip failed")
  168. zip.extractAll("test")