summary refs log tree commit diff stats
path: root/lib/impure/zipfiles.nim
blob: 029d8527d5b41ca071cafb87f6c724e353ecf9da (plain) (blame)
1
2
3
4
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bol
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements a zip archive creator/reader/modifier.

import 
  streams, libzip, times, os

type
  TZipArchive* = object of TObject ## represents a zip archive
    mode: TFileMode
    w: PZip


proc zipError(z: var TZipArchive) = 
  var e: ref EIO
  new(e)
  e.msg = $zip_strerror(z.w)
  raise e
  
proc open*(z: var TZipArchive, filename: string, mode: TFileMode = fmRead): bool =
  ## Opens a zip file for reading, writing or appending. All file modes are 
  ## supported. Returns true iff successful, false otherwise.
  var err, flags: int32
  case mode
  of fmRead, fmReadWriteExisting, fmAppend: flags = 0
  of fmWrite:                               
    if existsFile(filename): removeFile(filename)
    flags = ZIP_CREATE or ZIP_EXCL
  of fmReadWrite: flags = ZIP_CREATE
  z.w = zip_open(filename, flags, addr(err))
  z.mode = mode
  result = z.w != nil

proc close*(z: var TZipArchive) =
  ## Closes a zip file.
  zip_close(z.w)
 
proc createDir*(z: var TZipArchive, dir: string) = 
  ## Creates a directory within the `z` archive. This does not fail if the
  ## directory already exists. Note that for adding a file like 
  ## ``"path1/path2/filename"`` it is not necessary
  ## to create the ``"path/path2"`` subdirectories - it will be done 
  ## automatically by ``addFile``. 
  assert(z.mode != fmRead) 
  discard zip_add_dir(z.w, dir)
  zip_error_clear(z.w)

proc addFile*(z: var TZipArchive, dest, src: string) = 
  ## Adds the file `src` to the archive `z` with the name `dest`. `dest`
  ## may contain a path that will be created. 
  assert(z.mode != fmRead) 
  var zipsrc = zip_source_file(z.w, src, 0, -1)
  if zipsrc == nil:
    #echo("Dest: " & dest)
    #echo("Src: " & src)
    zipError(z)
  if zip_add(z.w, dest, zipsrc) < 0'i32:
    zip_source_free(zipsrc)
    zipError(z)

proc addFile*(z: var TZipArchive, file: string) = 
  ## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is
  ## the name of the destination.
  addFile(z, file, file)
  
proc mySourceCallback(state, data: pointer, len: int, 
                      cmd: Tzip_source_cmd): int {.cdecl.} = 
  var src = cast[PStream](state)
  case cmd
  of ZIP_SOURCE_OPEN: 
    if src.setPositionImpl != nil: setPosition(src, 0) # reset
  of ZIP_SOURCE_READ:
    result = readData(src, data, len)
  of ZIP_SOURCE_CLOSE: close(src)
  of ZIP_SOURCE_STAT: 
    var stat = cast[PZipStat](data)
    zip_stat_init(stat)
    stat.size = high(int32)-1 # we don't know the size
    stat.mtime = getTime()
    result = sizeof(TZipStat)
  of ZIP_SOURCE_ERROR:
    var err = cast[ptr array[0..1, cint]](data)
    err[0] = ZIP_ER_INTERNAL
    err[1] = 0
    result = 2*sizeof(cint)
  of constZIP_SOURCE_FREE: GC_unref(src)
  else: assert(false)
  
proc addFile*(z: var TZipArchive, dest: string, src: PStream) = 
  ## Adds a file named with `dest` to the archive `z`. `dest`
  ## may contain a path. The file's content is read from the `src` stream.
  assert(z.mode != fmRead)
  GC_ref(src)
  var zipsrc = zip_source_function(z.w, mySourceCallback, cast[pointer](src))
  if zipsrc == nil: zipError(z)
  if zip_add(z.w, dest, zipsrc) < 0'i32:
    zip_source_free(zipsrc)
    zipError(z)
  
# -------------- zip file stream ---------------------------------------------

type
  TZipFileStream = object of TStream
    f: Pzip_file

  PZipFileStream* = 
    ref TZipFileStream ## a reader stream of a file within a zip archive 

proc fsClose(s: PStream) = zip_fclose(PZipFileStream(s).f)
proc fsReadData(s: PStream, buffer: pointer, bufLen: int): int = 
  result = zip_fread(PZipFileStream(s).f, buffer, bufLen)

proc newZipFileStream(f: PZipFile): PZipFileStream = 
  new(result)
  result.f = f
  result.closeImpl = fsClose
  result.readDataImpl = fsReadData
  # other methods are nil!

# ----------------------------------------------------------------------------
  
proc getStream*(z: var TZipArchive, filename: string): PZipFileStream = 
  ## returns a stream that can be used to read the file named `filename`
  ## from the archive `z`. Returns nil in case of an error.
  ## The returned stream does not support the `setPosition`, `getPosition`, 
  ## `writeData` or `atEnd` methods.
  var x = zip_fopen(z.w, filename, 0'i32)
  if x != nil: result = newZipFileStream(x)
  
iterator walkFiles*(z: var TZipArchive): string = 
  ## walks over all files in the archive `z` and returns the filename 
  ## (including the path).
  var i = 0'i32
  var num = zip_get_num_files(z.w)
  while i < num:
    yield $zip_get_name(z.w, i, 0'i32)
    inc(i)


proc extractFile*(z: var TZipArchive, srcFile: string, dest: PStream) =
  ## extracts a file from the zip archive `z` to the destination stream.
  var strm = getStream(z, srcFile)
  while true:
    if not strm.atEnd:
        dest.write(strm.readStr(1))
    else: break
  dest.flush()
  strm.close()

proc extractFile*(z: var TZipArchive, srcFile: string, dest: string) =
  ## extracts a file from the zip archive `z` to the destination filename.
  var file = newFileStream(dest, fmReadWrite)
  extractFile(z, srcFile, file)
  file.close()

proc extractAll*(z: var TZipArchive, dest: string) =
  ## extracts all files from archive `z` to the destination directory.
  for file in walkFiles(z):
    extractFile(z, file, dest / extractFilename(file))