Commits

Sergey Astanin committed 1be9756

Split the project into bindings-libzip (low-level) and LibZip (high-level monadic API).

Comments (0)

Files changed (35)

 *.swp
 *~
 *.{chs.c,chi,hs,hi,o,h,tix}
-dist
-.hpc
+LibZip/dist
+bindings-libzip/dist
+*.hpc

Codec/Archive/LibZip.hs

-{- | Monadic interface to @libzip@.
-
-Most of the operations on zip archive happen within 'Archive' monad
-(see 'withArchive').
-Partial reading of the files in the archive may be performed from
-within 'Entry' monad (see 'fromFile').
-Both 'Archive' and 'Entry' are monad transformers over 'IO', and allow
-for IO with single and double 'lift'ing respectingly.
-
-Note: LibZip does not handle text encodings. Even if its API accepts
-'String's (e.g. in 'sourceBuffer'), character codes above 255 should
-not be used.  The user is responsible of proper encoding the text
-data.
-
-/Examples/
-
-List files in the zip archive:
-
-@
-import System.Environment (getArgs)
-import Codec.Archive.LibZip
-
-main = do
-  (zipfile:_) <- getArgs
-  files <- withArchive [] zipfile $ fileNames []
-  mapM_ putStrLn files
-@
-
-Create a zip archive and a add file to the archive:
-
-@
-import System.Environment (getArgs)
-import Codec.Archive.LibZip
-
-main = do
-  (zipfile:_) <- getArgs
-  withArchive [CreateFlag] zipfile $ do
-     zs <- sourceBuffer \"Hello World!\"
-     addFile \"hello.txt\" zs
-@
-
-Extract and print a file from the zip archive:
-
-@
-import System.Environment (getArgs)
-import Codec.Archive.LibZip
-
-main = do
-  (zipfile:file:_) <- getArgs
-  bytes <- withArchive [] zipfile $ fileContents [] file
-  putStrLn bytes
-@
-
-See also an implementation of a simple zip archiver @hzip.hs@ in the
-@examples/@ directory of the source distribution.
-
--}
-module Codec.Archive.LibZip
-    (
-    -- * Types
-      Archive
-    , Entry
-    , ZipStat(..)
-    -- * Archive operations
-    , withArchive, getZip
-    , numFiles, fileName, nameLocate, fileNames
-    , fileSize, fileSizeIx
-    , fileStat, fileStatIx
-    , deleteFile, deleteFileIx
-    , renameFile, renameFileIx
-    , addFile, addDirectory
-    , replaceFile, replaceFileIx
-    , sourceBuffer, sourceFile, sourceZip
-    , PureSource(..), sourcePure
-    , getComment, setComment, removeComment
-    , getFileComment, getFileCommentIx
-    , setFileComment, setFileCommentIx
-    , removeFileComment, removeFileCommentIx
-    , unchangeFile, unchangeFileIx
-    , unchangeArchive, unchangeAll
-    -- * File reading operations
-    , fromFile, fromFileIx
-    , readBytes, skipBytes, readContents
-    , fileContents, fileContentsIx
-    -- * Flags and options
-    , OpenFlag(..)
-    , FileFlag(..)
-    , ZipCompMethod(..)
-    , ZipEncryptionMethod(..)
-    -- * Exception handling
-    , ZipError(..)
-    , catchZipError
-    -- * Re-exports
-    , lift
-    ) where
-
-import Codec.Archive.LibZip.LowLevel
-import Codec.Archive.LibZip.Types
-import Codec.Archive.LibZip.Errors
-
-import Data.Time.Clock (UTCTime, getCurrentTime)
-import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
-import Data.Word (Word8)
-import Control.Monad.State.Strict
-    (StateT(..), MonadState(..), MonadTrans(..), lift, liftM)
-import Foreign.C.Error (Errno(..), eINVAL)
-import Foreign.C.String (withCString, withCStringLen, peekCString)
-import Foreign.C.Types (CInt, CSize)
-import Foreign.Marshal.Alloc (alloca)
-import Foreign.Marshal.Array (allocaArray, peekArray, withArrayLen, pokeArray)
-import Foreign.Marshal.Utils (with)
-import Foreign.Ptr (Ptr, nullPtr, castPtr)
-import Foreign.Storable (Storable, peek, poke, pokeElemOff, sizeOf)
-import qualified Control.Exception as E
-
---
--- Types
---
-
--- | Monadic computation with a zip archive. See 'withArchive'.
-type Archive a = StateT Zip IO a
-
--- | Monadic computation to read from open archive entries.
--- See 'fromFile' and 'fromFileIx'.
-type Entry a = StateT
-    (ZipFile,Int,[FileFlag])   -- (file, position index, access flags)
-    (StateT Zip IO)            -- archive monad
-    a
-
---
--- Archive operations
---
-
--- | Top-level wrapper for operations with an open
--- archive. 'withArchive' opens and closes the file
--- automatically. On error it throws 'ZipError'.
-withArchive :: [OpenFlag]  -- ^ Checks for consistency or existence.
-            -> FilePath    -- ^ Filename of the zip archive.
-            -> Archive a   -- ^ Action to do with the archive.
-            -> IO a
-withArchive flags path action =
-  withCString path $ \path' ->
-  alloca $ \errp ->
-  c'zip_open path' (combine flags) errp >>= \z ->
-  if z == nullPtr
-    then peek errp >>= E.throwIO. errFromCInt
-    else do
-      r <- fst `liftM` runStateT action z
-      e <- c'zip_close z
-      if e /= 0
-        then get_error z >>= E.throwIO
-        else return r
-
--- | Get the number of entries in the archive.
-numFiles :: Archive Int
-numFiles = do
-  z <- getZip
-  lift $ fromIntegral `liftM` c'zip_get_num_files z
-
--- | Get name of an entry in the archive by its index.
-fileName :: [FileFlag]  -- ^ 'FileUNCHANGED' flag can be used.
-         -> Int         -- ^ Position index of a file in the archive.
-         -> Archive FilePath  -- ^ Name of the file in the archive.
-fileName flags i = do
-  z <- getZip
-  lift $ do
-    n <- c'zip_get_name z (fromIntegral i) (combine flags)
-    doIf' (n /= nullPtr) z $ peekCString n
-
--- | Locate an entry (get its index) in the archive by its name.
-nameLocate :: [FileFlag]  -- ^ Filename lookup mode.
-           -> FilePath    -- ^ Name of the file in the archive.
-           -> Archive (Maybe Int)  -- ^ 'Just' position index if found.
-nameLocate flags name = do
-  z <- getZip
-  lift $
-    withCString name $ \name' -> do
-    i <- fromIntegral `liftM` c'zip_name_locate z name' (combine flags)
-    if i < 0
-       then return Nothing
-       else return (Just i)
-
--- | Get names of all entries (files and directories) in the archive.
-fileNames :: [FileFlag]  -- ^ 'FileUNCHANGED' flag is accepted.
-          -> Archive [FilePath]
-fileNames flags = do
-  n <- numFiles
-  mapM (fileName flags) [0..n-1]
-
--- | Get size of a file in the archive.
-fileSize :: [FileFlag]  -- ^ Filename lookup mode, 'FileUNCHANGED' can be used.
-         -> FilePath    -- ^ Name of the file in the archive.
-         -> Archive Int -- ^ File size.
-fileSize flags name = fileStat flags name >>= return . zs'size
-
--- | Get size of a file in the archive (by index).
-fileSizeIx :: [FileFlag]  -- ^ 'FileUNCHANGED' is accepted.
-           -> Int         -- ^ Position index of a file in the archive.
-           -> Archive Int -- ^ File size.
-fileSizeIx flags i = fileStatIx flags i >>= return . zs'size
-
--- | Get information about a file in the archive.
-fileStat :: [FileFlag]  -- ^ Filename lookup mode, 'FileUNCHANGED' can be used.
-         -> FilePath    -- ^ Name of the file in the archive.
-         -> Archive ZipStat  -- ^ Infomation about the file.
-fileStat flags name = do
-  z <- getZip
-  lift $
-       withCString name $ \name' ->
-       alloca $ \stat -> do
-       c'zip_stat_init stat
-       r <- c'zip_stat z name' (combine flags) stat
-       doIf' (r == 0) z $ toZipStat =<< peek stat
-
--- | Get information about a file in the archive (by index).
-fileStatIx :: [FileFlag]  -- ^ 'FileUNCHANGED' can be used.
-           -> Int         -- ^ Position index of a file in the archive.
-           -> Archive ZipStat  -- ^ Information about the file.
-fileStatIx flags i = do
-  z <- getZip
-  lift $
-       alloca $ \stat -> do
-       r <- c'zip_stat_index z (fromIntegral i) (combine flags) stat
-       doIf' (r == 0) z $ toZipStat =<< peek stat
-
--- | Delete file from the archive.
-deleteFile :: [FileFlag]  -- ^ Filename lookup mode.
-           -> FilePath    -- ^ Filename.
-           -> Archive ()
-deleteFile flags name = do
-  mbi <- nameLocate flags name
-  maybe (lift $ E.throwIO ErrNOENT) deleteFileIx mbi
-
--- | Delete file (referenced by position index) from the archive.
-deleteFileIx :: Int  -- ^ Position index of a file in the archive.
-             -> Archive ()
-deleteFileIx i = do
-  z <- getZip
-  r <- lift $ c'zip_delete z (fromIntegral i)
-  if r == 0
-     then return ()
-     else lift $ get_error z >>= E.throwIO
-
--- | Rename file in the archive.
-renameFile :: [FileFlag]  -- ^ Filename lookup mode.
-           -> FilePath    -- ^ Old name.
-           -> FilePath    -- ^ New name.
-           -> Archive ()
-renameFile flags oldname newname = do
-  mbi <- nameLocate flags oldname
-  maybe (lift $ E.throwIO ErrNOENT) (\i -> renameFileIx i newname) mbi
-
--- | Rename file (referenced by position index) in the archive.
-renameFileIx :: Int  -- ^ Position index of a file in the archive.
-             -> FilePath -- ^ New name.
-             -> Archive ()
-renameFileIx i newname = do
-  z <- getZip
-  r <- lift $ withCString newname $ c'zip_rename z (fromIntegral i)
-  if r == 0
-     then return ()
-     else lift $ get_error z >>= E.throwIO
-
--- | Add a file to the archive.
-addFile :: FilePath   -- ^ Name of the file to create.
-        -> ZipSource  -- ^ Source where file data is obtained from.
-        -> Archive Int  -- ^ Position index of the new file.
-addFile name src = do
-  z <- getZip
-  lift $ withCString name $ \name' -> do
-    i <- c'zip_add z name' src
-    if i < 0
-       then c'zip_source_free src >> get_error z >>= E.throwIO
-       else return $ fromIntegral i
-
--- | Add a directory to the archive.
-addDirectory :: FilePath     -- ^ Directory's name in the archive.
-             -> Archive Int  -- ^ Position index of the new directory entry.
-addDirectory name = do
-  z <- getZip
-  r <- lift $ withCString name $ c'zip_add_dir z
-  if r < 0
-     then lift $ get_error z >>= E.throwIO
-     else return (fromIntegral r)
-
--- | Replace a file in the archive.
-replaceFile :: [FileFlag]  -- ^ Filename lookup mode.
-            -> FilePath    -- ^ File to replace.
-            -> ZipSource   -- ^ Source where the new file data is obtained from.
-            -> Archive ()
-replaceFile flags name src = do
-  mbi <- nameLocate flags name
-  maybe (lift $ c'zip_source_free src >> E.throwIO ErrNOENT)
-        (\i -> replaceFileIx i src >> return ()) mbi
-
--- | Replace a file in the archive (referenced by position index).
-replaceFileIx :: Int       -- ^ Position index of a file in the archive.
-              -> ZipSource -- ^ Source where the new file data is obtained from
-              -> Archive ()
-replaceFileIx i src = do
-  z <- getZip
-  lift $ do
-    r <- c'zip_replace z (fromIntegral i) src
-    if r < 0
-       then c'zip_source_free src >> get_error z >>= E.throwIO
-       else return ()
-
--- | Create a data source. Note: input is converted to @[Word8]@ internally.
-sourceBuffer :: (Enum a)
-             => [a]
-             -> Archive ZipSource
-sourceBuffer src = do
-  let ws = map (toEnum . fromEnum) src :: [Word8]
-  z <- getZip
-  lift $ withArrayLen ws $ \len buf -> do
-      zs <- c'zip_source_buffer z (castPtr buf) (fromIntegral len) 0
-      if zs == nullPtr
-         then get_error z >>= E.throwIO
-         else return zs
-
--- | Create a data source from a file.
-sourceFile :: FilePath   -- ^ File to open.
-           -> Int        -- ^ Offset from the beginning of the file.
-           -> Int        -- ^ The number of bytes to read. If @0@ or @-1@,
-                         -- the read till the end of file.
-           -> Archive ZipSource
-sourceFile name offset len = do
-  z <- getZip
-  lift $ withCString name $ \name' -> do
-      zs <- c'zip_source_file z name' (fromIntegral offset) (fromIntegral len)
-      if zs == nullPtr
-         then get_error z >>= E.throwIO
-         else return zs
-
--- | Create a data source from a file in the zip archive.
-sourceZip :: [FileFlag]  -- ^ 'FileUNCHANGED' and 'FileRECOMPRESS' can be used.
-          -> Zip         -- ^ Source archive.
-          -> Int         -- ^ Position index of a file in the source archive.
-          -> Int         -- ^ Offset from the beginning of the file.
-          -> Int         -- ^ The number of bytes to read. If @0@ or @-1@,
-                         -- then read till the end of file.
-          -> Archive ZipSource
-sourceZip flags srcz srcidx offset len = do
-  z <- getZip
-  lift $ do
-    zs <- c'zip_source_zip z srcz (fromIntegral srcidx)
-          (combine flags) (fromIntegral offset) (fromIntegral len)
-    if zs == nullPtr
-       then get_error z >>= E.throwIO
-       else return zs
-
--- | Create a data source from a 'PureSource'.
--- Note: input of @[a]@ is converted to @[Word8]@ internally.
-sourcePure :: (Enum a, Storable a, Storable st)
-           => PureSource a st -> Archive ZipSource
-sourcePure pureSrc = do
-  z <- getZip
-  lift $ do
-    cb <- mk'zip_source_callback (runPureSource pureSrc)
-    zs <- with (srcState pureSrc) $
-          \pState -> c'zip_source_function z cb (castPtr pState)
-    if zs == nullPtr
-       then get_error z >>= E.throwIO
-       else return zs
-
--- | Wrapper for a user-provided pure function to be used with 'sourcePure'.
--- Data size should be known in advance ('srcSize').
--- The function should support reading by chunks ('readSrc').
-data (Enum a, Storable a, Storable st) => PureSource a st = PureSource {
-    srcState :: st   -- ^ Initial state of the source.
-  , srcSize  :: Int  -- ^ Total size of the data.
-  , srcMTime :: Maybe UTCTime  -- ^ Modification time (current time if Nothing).
-  , readSrc  :: Int -> st -> Maybe (Int, [a], st) -- ^ Read a chunk of the data,
-                -- return @Just@ the size of data read, the data themselves and
-                -- the new state of the source, or @Nothing@ on error.
-  }
-
-runPureSource :: (Enum a, Storable a, Storable st)
-              => PureSource a st
-              -> (Ptr () -> Ptr () -> CSize -> C'zip_source_cmd -> IO CSize)
-runPureSource src pState pData len cmd
-  | cmd == c'ZIP_SOURCE_OPEN = return 0
-  | cmd == c'ZIP_SOURCE_READ = do
-      state <- peek (castPtr pState :: Ptr st)
-      case readSrc (src { srcState = state }) (fromIntegral len) state of
-        Just (len',bs,state') -> do
-          pokeArray (castPtr pData :: Ptr Word8) (map (toEnum.fromEnum) bs)
-          poke (castPtr pState) state'
-          return (fromIntegral len')
-        Nothing -> return (-1)
-  | cmd == c'ZIP_SOURCE_CLOSE = return 0
-  | cmd == c'ZIP_SOURCE_STAT = do
-      t <- maybe getCurrentTime return (srcMTime src)
-      let pt = fromInteger . round . utcTimeToPOSIXSeconds $ t
-      let pStat = castPtr pData
-      c'zip_stat_init pStat
-      stat <- peek pStat
-      let stat' = stat { c'zip_stat'mtime = pt
-                       , c'zip_stat'size = fromIntegral $ srcSize src }
-      poke pStat stat'
-      return $ fromIntegral (sizeOf stat')
-  | cmd == c'ZIP_SOURCE_ERROR = do
-      let pErrs = castPtr pData :: Ptr CInt
-      poke pErrs (fromIntegral . fromEnum $ ErrINVAL)
-      let (Errno esys) = eINVAL
-      pokeElemOff pErrs 1 esys
-      return $ fromIntegral (2 * sizeOf esys)
-  | cmd == c'ZIP_SOURCE_FREE = return 0
-  | otherwise = return (-1)
-        
--- | Get zip archive comment.
-getComment :: [FileFlag]  -- ^ 'FileUNCHANGED' can be used.
-           -> Archive (Maybe String)
-getComment flags = do
-  z <- getZip
-  (c,n) <- lift $ alloca $ \lenp -> do
-         c <- c'zip_get_archive_comment z lenp (combine flags)
-         n <- peek lenp
-         return (c,n)
-  if  c == nullPtr
-    then return Nothing
-    else lift $ peekCString c >>= return . Just . take (fromIntegral n)
-
--- | Set zip archive comment.
-setComment :: String   -- ^ Comment message.
-           -> Archive ()
-setComment msg = do
-  z <- getZip
-  r <- lift $ withCStringLen msg $ \(msg',i') ->
-       c'zip_set_archive_comment z msg' (fromIntegral i')
-  if r < 0
-     then lift $ get_error z >>= E.throwIO
-     else return ()
-
--- | Remove zip archive comment.
-removeComment :: Archive ()
-removeComment = do
-  z <- getZip
-  r <- lift $ c'zip_set_archive_comment z nullPtr 0
-  if r < 0
-     then lift $ get_error z >>= E.throwIO
-     else return ()
-
--- | Get comment for a file in the archive.
-getFileComment :: [FileFlag]  -- ^ Filename lookup mode.
-               -> FilePath    -- ^ Filename
-               -> Archive (Maybe String)
-getFileComment flags name = do
-  mbi <- nameLocate flags name
-  maybe (lift $ E.throwIO ErrNOENT) (getFileCommentIx flags) mbi
-
--- | Get comment for a file in the archive (referenced by position index).
-getFileCommentIx :: [FileFlag]  -- ^ FileUNCHANGED can be used.
-                 -> Int         -- ^ Position index of the file.
-                 -> Archive (Maybe String)
-getFileCommentIx flags i = do
-  z <- getZip
-  (c,n) <- lift $ alloca $ \lenp -> do
-           c <- c'zip_get_file_comment z (fromIntegral i) lenp (combine flags)
-           n <- peek lenp
-           return (c,n)
-  if c == nullPtr
-     then return Nothing
-     else lift $ peekCString c >>= return . Just . take (fromIntegral n)
-
--- | Set comment for a file in the archive.
-setFileComment :: [FileFlag]   -- ^ Name lookup mode.
-               -> FilePath     -- ^ Filename.
-               -> String       -- ^ New file comment.
-               -> Archive ()
-setFileComment flags path comment = do
-  mbi <- nameLocate flags path
-  maybe (lift $ E.throwIO ErrNOENT) (flip setFileCommentIx comment) mbi
-
--- | Set comment for a file in the archive (referenced by position index).
-setFileCommentIx :: Int        -- ^ Position index of a file in the archive.
-                 -> String     -- ^ New file comment. 
-                 -> Archive ()
-setFileCommentIx i comment = do
-  z <- getZip
-  r <- lift $ withCStringLen comment $ \(msg,len) ->
-       c'zip_set_file_comment z (fromIntegral i) msg (fromIntegral len)
-  if r < 0
-     then lift $ get_error z >>= E.throwIO
-     else return ()
-
--- | Remove comment for a file in the archive.
-removeFileComment :: [FileFlag]  -- ^ Filename lookup mode.
-                  -> FilePath    -- ^ Filename.
-                  -> Archive ()
-removeFileComment flags path = do
-  mbi <- nameLocate flags path
-  maybe (lift $ E.throwIO ErrNOENT) removeFileCommentIx mbi
-
--- | Remove comment for a file in the archive (referenced by position index).
-removeFileCommentIx :: Int  -- ^ Position index of a file in the archive.
-                    -> Archive ()
-removeFileCommentIx i = do
-  z <- getZip
-  r <- lift $ c'zip_set_file_comment z (fromIntegral i) nullPtr 0
-  if r < 0
-     then lift $ get_error z >>= E.throwIO
-     else return ()
-
--- | Undo changes to a file in the archive.
-unchangeFile :: [FileFlag]  -- ^ Filename lookup mode.
-             -> FilePath    -- ^ Filename.
-             -> Archive ()
-unchangeFile flags name = do
-  mbi <- nameLocate flags name
-  maybe (lift $ E.throw ErrNOENT) unchangeFileIx mbi
-
--- | Undo changes to a file in the archive (referenced by position index).
-unchangeFileIx :: Int  -- ^ Position index of a file in the archive.
-               -> Archive ()
-unchangeFileIx i = do
-  z <- getZip
-  lift $ do
-    r <- c'zip_unchange z (fromIntegral i)
-    if r < 0
-       then get_error z >>= E.throwIO
-       else return ()
-
--- | Undo global changes to zip archive (revert changes to the archive
--- comment and global flags).
-unchangeArchive :: Archive ()
-unchangeArchive = do
-  z <- getZip
-  lift $ do
-    r <- c'zip_unchange_archive z
-    if r < 0
-       then get_error z >>= E.throwIO
-       else return ()
-
--- | Undo all changes in a zip archive.
-unchangeAll :: Archive ()
-unchangeAll = do
-  z <- getZip
-  lift $ do
-    r <- c'zip_unchange_all z
-    if r < 0
-       then get_error z >>= E.throwIO
-       else return ()
-
---
--- File reading operations
---
-
--- | Wrapper for operations with a file in the archive. 'fromFile' is normally
--- called from within an 'Archive' action (see also 'withArchive').
--- 'fromFile' can be replaced with 'fileContents' to read an entire file at
--- once.
-fromFile :: [FileFlag]  -- ^ Filename lookup mode,
-                        -- 'FileCOMPRESSED' and 'FileUNCHANGED' can be used.
-         -> FilePath    -- ^ Name of the file in the arhive.
-         -> Entry a     -- ^ Action with the file.
-         -> Archive a
-fromFile flags name action = do
-    z <- getZip
-    nameLocate flags name >>= maybe (lift $ get_error z >>= E.throwIO) runAction
-  where
-    runAction i = do
-      z <- getZip
-      zf <- lift $ withCString name $ \n -> c'zip_fopen z n (combine flags)
-      if zf == nullPtr
-        then lift $ get_error z >>= E.throwIO
-        else do
-          r <- fst `liftM` runStateT action (zf,i,flags)
-          e <- lift $ c'zip_fclose zf
-          if e /= 0
-            then lift $ E.throwIO $ (toEnum . fromIntegral $ e :: ZipError)
-            else return r
-
--- | Wrapper for operations with a file in the archive. File is referenced
--- by index (position). 'fromFileIx' is normally called from within
--- an 'Archive' action (see also 'withArchive'). 'fromFileIx' can be replaced
--- with 'fileContentsIx' to read an entire file at once.
-fromFileIx :: [FileFlag] -- ^ 'FileCOMPRESSED' and 'FileUNCHANGED' can be used.
-           -> Int        -- ^ Position index of a file in the archive.
-           -> Entry a    -- ^ Action with the file.
-           -> Archive a
-fromFileIx flags i action = do
-  z <- getZip
-  zf <- lift $ c'zip_fopen_index z (fromIntegral i) (combine flags)
-  if zf == nullPtr
-     then lift $ get_error z >>= E.throwIO
-     else do
-       r <- fst `liftM` runStateT action (zf,i,flags)
-       e <- lift $ c'zip_fclose zf
-       if e /= 0
-          then lift $ E.throwIO $ (toEnum . fromIntegral $ e :: ZipError)
-          else return r
-
--- | Read at most @n@ bytes from the file.
-readBytes ::
-    (Enum a)
-    => Int       -- ^ The number of bytes to read.
-    -> Entry [a] -- ^ Bytes read.
-readBytes n = do
-  (zf,_,_) <- get
-  lift . lift $ allocaArray n $ \buf -> do
-         nread <- c'zip_fread zf (castPtr buf) (fromIntegral n)
-         if nread < 0
-            then
-              get_file_error zf >>= E.throwIO
-            else do
-              bs <- peekArray (fromIntegral nread) buf :: IO [Word8]
-              return . map (toEnum . fromEnum) $ bs
-
--- | Skip @n@ bytes from the open file. Note: this is not faster than reading.
-skipBytes :: Int -> Entry ()
-skipBytes n = (readBytes n :: Entry [Word8]) >> return ()
-
--- | Read entire file contents.
-readContents ::
-    (Enum a)
-    => Entry [a]  -- ^ Contents of the file.
-readContents = do
-  (_,i,flags) <- get
-  sz <- lift $ fileSizeIx flags i
-  readBytes sz
-
--- | Read entire file. Shortcut for 'readContents' from within 'Archive' monad.
-fileContents :: (Enum a)
-    => [FileFlag]
-    -> FilePath
-    -> Archive [a]
-fileContents flags name = fromFile flags name readContents
-
--- | Read entire file (referenced by position index). Shortcut for
--- 'readContents' from within 'Archive' monad.
-fileContentsIx :: (Enum a)
-    => [FileFlag]
-    -> Int
-    -> Archive [a]
-fileContentsIx flags i = fromFileIx flags i readContents
-
---
--- Helpers
---
-
--- | Get archive handler. Throw 'ErrINVAL' if the archive is closed.
-getZip :: Archive Zip
-getZip = do
-  z <- get
-  if z == nullPtr
-     then lift $ E.throwIO ErrINVAL
-     else return z
-
--- | Get and throw a 'ZipError' if condition fails. Otherwise work normally.
-doIf :: Bool -> Zip -> (Zip -> IO a) -> IO a
-doIf cnd z action =
-    if cnd
-       then action z
-       else get_error z >>= E.throwIO
-
--- | Get and throw a 'ZipError' if condition fails. See also 'doIf'.
-doIf' :: Bool -> Zip -> (IO a) -> IO a
-doIf' cnd z action = doIf cnd z (const action)

Codec/Archive/LibZip/Errors.hs

--- | Error handling functions.
-module Codec.Archive.LibZip.Errors
-    ( errFromCInt
-    , get_error
-    , get_file_error
-    , catchZipError
-    ) where
-
-import Data.Typeable (Typeable, typeOf)
-import Foreign.C.Types
-import Foreign.Marshal.Alloc (alloca)
-import Foreign.Ptr (nullPtr)
-import Foreign.Storable (peek)
-import qualified Control.Exception as E
-
-import Codec.Archive.LibZip.LowLevel
-import Codec.Archive.LibZip.Types
-
-errFromCInt :: CInt -> ZipError
-errFromCInt = toEnum . fromEnum
-
-get_error :: Zip -> IO ZipError
-get_error z | z == nullPtr = E.throwIO ErrINVAL
-get_error z = alloca $ \zep -> do
-   c'zip_error_get z zep nullPtr
-   peek zep >>= return . errFromCInt
-
-get_file_error :: ZipFile -> IO ZipError
-get_file_error zf
-    | zf == nullPtr = E.throwIO ErrINVAL
-    | otherwise = alloca $ \zep -> do
-         c'zip_file_error_get zf zep nullPtr
-         peek zep >>= return . errFromCInt
-                                    
--- | Wrapper to catch library errors.
-catchZipError :: IO a -> (ZipError -> IO a) -> IO a
-catchZipError f h = E.catchJust ifZipError f h
-  where
-    ifZipError :: (Typeable e, E.Exception e) => e -> Maybe e
-    ifZipError x | typeOf x == typeOf ErrOK = Just x
-    ifZipError _ | otherwise = Nothing
-

Codec/Archive/LibZip/LegacyZeroZero.hsc

--- | This module is a backwards compatible replacement for @Codec.Archive.LibZip@ module of LibZip 0.0.x. This API is deprecated, please don't use it in new code.
-module Codec.Archive.LibZip.LegacyZeroZero (
-  -- * Types
-  Zip,ZipFile,OpenFlag(..),FileFlag(..),ZipError(..)
-  ,Word8
-  -- * High-level interface
-  ,withZip,getFiles,getFileSize
-  ,readZipFile,readZipFile'
-  ,readZipFileHead,readZipFileHead'
-  -- * Low-level bindings
-  ,open,close,get_num_files,get_name
-  ,fopen,fopen_index,fclose,fread
-   -- * Utilities
-   ,catchZipError,isFile,isDir
-  ) where
-
-import Control.Monad (liftM)
-import Data.Word (Word8)
-import Foreign.C.String (withCString,peekCString)
-import Foreign.Marshal.Alloc (alloca)
-import Foreign.Marshal.Array (allocaArray, peekArray)
-import Foreign.Ptr (Ptr, nullPtr, castPtr)
-import Foreign.Storable (peek)
-import qualified Control.Exception as E
-import qualified Data.ByteString as B
-
-import Codec.Archive.LibZip.LowLevel
-import Codec.Archive.LibZip.Types
-import Codec.Archive.LibZip.Errors
-
--- | Open zip archive specified by /path/ and return its handler on success.
-open :: String   -- ^ /path/ of the file to open
-     -> [OpenFlag]  -- ^ open mode
-     -> IO Zip  -- ^ handler of the open zip archive
-open path flags =
-  withCString path $ \path' ->
-  alloca $ \errp -> do
-  z <- c'zip_open path' (combine flags) errp
-  if z /= nullPtr
-    then return z
-    else peek errp >>= E.throwIO . errFromCInt
-
--- | Close zip archive.
-close :: Zip -> IO ()
-close z | z == nullPtr = E.throwIO ErrINVAL
-close z = do
-  r <- c'zip_close z
-  if r == 0
-    then return ()
-    else E.throwIO =<< get_error z
-
--- | Return the number of files in the archive.
-get_num_files :: Zip -> IO Int
-get_num_files z | z == nullPtr = E.throwIO ErrINVAL
-get_num_files z = fromIntegral `liftM` c'zip_get_num_files z
-
--- | Get name of file by index.
-get_name :: Zip -> Int -> [FileFlag] -> IO String
-get_name z _ _ | z == nullPtr = E.throwIO ErrINVAL
-get_name z i flags = do
-  n <- c'zip_get_name z (fromIntegral i) (combine flags)
-  if n /= nullPtr
-    then peekCString n
-    else E.throwIO =<< get_error z
-
--- | Open file in zip archive for reading.
-fopen :: Zip -> String -> [FileFlag] -> IO ZipFile
-fopen z _ _ | z == nullPtr = E.throwIO ErrINVAL
-fopen z fn flags = withCString fn $ \fn' ->
-  returnNotNull z =<< c'zip_fopen z fn' (combine flags)
-
--- | Open n-th file in zip archive for reading.
-fopen_index :: Zip -> Int -> [FileFlag] -> IO ZipFile
-fopen_index z _ _ | z == nullPtr = E.throwIO ErrINVAL
-fopen_index z i flags =
-  returnNotNull z =<< c'zip_fopen_index z (fromIntegral i) (combine flags)
-
--- | Close file in zip archive.
-fclose :: ZipFile -> IO ()
-fclose zf =
-   errorOrNothing =<< c'zip_fclose zf
-   where errorOrNothing 0 = return ()
-         errorOrNothing e = E.throwIO (errFromCInt e)
-
--- | Read from file in zip archive.
-fread :: ZipFile -> Int -> IO [Word8]
-fread zf count =
-  allocaArray count $ \buf -> do
-  rcount <- c'zip_fread zf (castPtr buf) (fromIntegral count)
-  if rcount < 0
-    then E.throwIO ErrREAD
-    else peekArray (fromIntegral rcount) buf
-
--- High level Haskell wrappers
-
--- | Open zip archive, do something, and close the archive.
-withZip :: String -- ^ /path/ of the file to open
-        -> [OpenFlag] -- ^ open mode
-        -> (Zip -> IO a) -- ^ action to do on zip arhive
-        -> IO a
-withZip filename flags action = do
-  z <- open filename flags
-  result <- action z
-  close z
-  return result
-
--- | Get names of the files in archive.
-getFiles :: Zip -> [FileFlag] -> IO [String]
-getFiles z flags = do
-  n <- get_num_files z
-  mapM (\i -> get_name z i flags) [0..(n-1)]
-
--- | Get size of the file in archive.
-getFileSize :: Zip        -- ^ zip archive
-            -> String     -- ^ name of the file in the archive
-            -> [FileFlag] -- ^ file name mode
-            -> IO Int
-getFileSize z name flags =
-  withCString name $ \name' ->
-  alloca $ \stat -> do
-  ret <- c'zip_stat z name' (combine flags) stat
-  if ret /= 0
-    then E.throwIO =<< get_error z
-    else return . fromIntegral . c'zip_stat'size =<< peek stat
-
--- | Read uncompressed file from the archive. Produce a strict ByteString.
-readZipFile :: Zip -- ^ zip archive
-         -> String -- ^ name of the file in the archive
-         -> [FileFlag] -- ^ file name mode
-         -> IO B.ByteString
-readZipFile z fname flags = return . B.pack =<< readZipFile' z fname flags
-
--- | Read uncompressed file from the archive. Produce a list of 'Word8'.
-readZipFile' :: Zip -- ^ zip archive
-         -> String -- ^ name of the file in the archive
-         -> [FileFlag] -- ^ file name mode
-         -> IO [Word8]
-readZipFile' z fname flags = do
-  sz <- getFileSize z fname flags
-  readZipFileHead' z fname flags sz
-
--- | Read beginning of the uncompressed file from the archive. Produce a list of 'Word8'.
-readZipFileHead' :: Zip -- ^ zip archive
-         -> String -- ^ name of the file in the archive
-         -> [FileFlag] -- ^ file name mode
-         -> Int -- ^ how many bytes to read
-         -> IO [Word8]
-readZipFileHead' z fname flags n = do
-  f <- fopen z fname flags
-  bytes <- fread f n
-  fclose f
-  return bytes
-
--- | Read beginning of the uncompressed file from the archive. Produce a strict ByteString.
-readZipFileHead :: Zip -- ^ zip archive
-         -> String -- ^ name of the file in the archive
-         -> [FileFlag] -- ^ file name mode
-         -> Int -- ^ how many bytes to read
-         -> IO B.ByteString
-readZipFileHead z fname flags n = return . B.pack =<< readZipFileHead' z fname flags n
-
-
--- | Return True if path is a file name, not a directory name (does not end with '/').
-isFile :: String -> Bool
-isFile filename = (lastMay filename /= Just '/')
-
--- | Return True if path is a directory name (ends with '/').
-isDir :: String -> Bool
-isDir = not . isFile
-
-lastMay :: [a] -> Maybe a
-lastMay [] = Nothing
-lastMay xs = Just $ last xs
-
--- Return the second argument or throw the last libzip error.
-returnNotNull :: Zip -> Ptr a -> IO (Ptr a)
-returnNotNull z _ | z == nullPtr = E.throwIO ErrINVAL
-returnNotNull z ptr =
-  if ptr /= nullPtr
-    then return ptr
-    else E.throwIO =<< get_error z

Codec/Archive/LibZip/LowLevel.hsc

-#include <bindings.dsl.h>
-#include <zip.h>
-
--- | This module provides automatic low-level bindings to @libzip@ library.
--- See also:
---
---   * @libzip@ documention: <http://nih.at/libzip/libzip.html> and @zip.h@
---
---   * @bindings-DSL@ documentation:
---     <http://bitbucket.org/mauricio/bindings-dsl/wiki/Home>
-
-module Codec.Archive.LibZip.LowLevel where
-#strict_import
-
-#opaque_t zip
-#opaque_t zip_file
-#opaque_t zip_source
-
--- flags for zip_open
-
-#num ZIP_CREATE
-#num ZIP_EXCL
-#num ZIP_CHECKCONS
-
--- flags for zip_name_locate, zip_fopen, zip_stat, ...
-
-#num ZIP_FL_NOCASE
-#num ZIP_FL_NODIR
-#num ZIP_FL_COMPRESSED
-#num ZIP_FL_UNCHANGED
-#num ZIP_FL_RECOMPRESS
-
--- archive global flags flags
-
-#num ZIP_AFL_TORRENT
-
--- libzip error codes
-
-#num ZIP_ER_OK
-#num ZIP_ER_MULTIDISK
-#num ZIP_ER_RENAME
-#num ZIP_ER_CLOSE
-#num ZIP_ER_SEEK
-#num ZIP_ER_READ
-#num ZIP_ER_WRITE
-#num ZIP_ER_CRC
-#num ZIP_ER_ZIPCLOSED
-#num ZIP_ER_NOENT
-#num ZIP_ER_EXISTS
-#num ZIP_ER_OPEN
-#num ZIP_ER_TMPOPEN
-#num ZIP_ER_ZLIB
-#num ZIP_ER_MEMORY
-#num ZIP_ER_CHANGED
-#num ZIP_ER_COMPNOTSUPP
-#num ZIP_ER_EOF
-#num ZIP_ER_INVAL
-#num ZIP_ER_NOZIP
-#num ZIP_ER_INTERNAL
-#num ZIP_ER_INCONS
-#num ZIP_ER_REMOVE
-#num ZIP_ER_DELETED
-
--- type of system error value
-
-#num ZIP_ET_NONE
-#num ZIP_ET_SYS
-#num ZIP_ET_ZLIB
-
--- compression methods
-
-#num ZIP_CM_DEFAULT
-#num ZIP_CM_STORE
-#num ZIP_CM_SHRINK
-#num ZIP_CM_REDUCE_1
-#num ZIP_CM_REDUCE_2
-#num ZIP_CM_REDUCE_3
-#num ZIP_CM_REDUCE_4
-#num ZIP_CM_IMPLODE
-#num ZIP_CM_DEFLATE
-#num ZIP_CM_DEFLATE64
-#num ZIP_CM_PKWARE_IMPLODE
-#num ZIP_CM_BZIP2
-#num ZIP_CM_LZMA
-#num ZIP_CM_TERSE
-#num ZIP_CM_LZ77
-#num ZIP_CM_WAVPACK
-#num ZIP_CM_PPMD
-
--- encryption methods
-
-#num ZIP_EM_NONE
-#num ZIP_EM_TRAD_PKWARE
-#num ZIP_EM_UNKNOWN
-
-#integral_t enum zip_source_cmd
-#num ZIP_SOURCE_OPEN
-#num ZIP_SOURCE_READ
-#num ZIP_SOURCE_CLOSE
-#num ZIP_SOURCE_STAT
-#num ZIP_SOURCE_ERROR
-#num ZIP_SOURCE_FREE
-
--- typedef ssize_t (*zip_source_callback)(void *state, void *data,
---                                        size_t len, enum zip_source_cmd cmd);
-#callback zip_source_callback , Ptr () -> Ptr () -> CSize -> <zip_source_cmd> -> IO CSize
-
-#opaque_t time_t
-
-#starttype struct zip_stat
-#field name, Ptr CChar
-#field index, CInt
-#field crc, CUInt
-#field mtime, CTime
-#field size, CSize
-#field comp_size, CSize
-#field comp_method, CUShort
-#field encryption_method, CUShort
-#stoptype
-
--- int zip_add(struct zip *, const char *, struct zip_source *);
-#ccall zip_add , Ptr <zip> -> CString -> Ptr <zip_source> -> IO CInt
-
--- int zip_add_dir(struct zip *, const char *);
-#ccall zip_add_dir , Ptr <zip> -> CString -> IO CInt
-
--- int zip_close(struct zip *);
-#ccall zip_close , Ptr <zip> -> IO CInt
-
--- int zip_delete(struct zip *, int);
-#ccall zip_delete , Ptr <zip> -> CInt -> IO CInt
-
--- void zip_error_clear(struct zip *);
-#ccall zip_error_clear , Ptr <zip> -> IO ()
-
--- void zip_error_get(struct zip *, int *, int *);
-#ccall zip_error_get , Ptr <zip> -> Ptr CInt -> Ptr CInt -> IO ()
-
--- int zip_error_get_sys_type(int);
-#ccall zip_error_get_sys_type , CInt -> IO CInt
-
--- int zip_error_to_str(char *, size_t, int, int);
-#ccall zip_error_to_str , Ptr Char -> CSize -> CInt -> CInt -> IO CInt
-
--- int zip_fclose(struct zip_file *);
-#ccall zip_fclose , Ptr <zip_file> -> IO CInt
-
--- void zip_file_error_clear(struct zip_file *);
-#ccall zip_file_error_clear , Ptr <zip_file> -> IO ()
-
--- void zip_file_error_get(struct zip_file *, int *, int *);
-#ccall zip_file_error_get , Ptr <zip_file> -> Ptr CInt -> Ptr CInt -> IO ()
-
--- const char *zip_file_strerror(struct zip_file *);
-#ccall zip_file_strerror , Ptr <zip_file> -> IO CString
-
--- struct zip_file *zip_fopen(struct zip *, const char *, int);
-#ccall zip_fopen , Ptr <zip> -> CString -> CInt -> IO (Ptr <zip_file>)
-
--- struct zip_file *zip_fopen_index(struct zip *, int, int);
-#ccall zip_fopen_index , Ptr <zip> -> CInt -> CInt -> IO (Ptr <zip_file>)
-
--- ssize_t zip_fread(struct zip_file *, void *, size_t);
-#ccall zip_fread , Ptr <zip_file> -> Ptr () -> CSize -> IO CSize
-
--- const char *zip_get_archive_comment(struct zip *, int *, int);
-#ccall zip_get_archive_comment , Ptr <zip> -> Ptr CInt -> CInt -> IO CString
-
--- int zip_get_archive_flag(struct zip *, int, int);
-#ccall zip_get_archive_flag , Ptr <zip> -> CInt -> CInt -> IO CInt
-
--- const char *zip_get_file_comment(struct zip *, int, int *, int);
-#ccall zip_get_file_comment , Ptr <zip> -> CInt -> Ptr CInt -> CInt -> IO CString
-
--- const char *zip_get_name(struct zip *, int, int);
-#ccall zip_get_name , Ptr <zip> -> CInt -> CInt -> IO CString
-
--- int zip_get_num_files(struct zip *);
-#ccall zip_get_num_files , Ptr <zip> -> IO CInt
-
--- int zip_name_locate(struct zip *, const char *, int);
-#ccall zip_name_locate , Ptr <zip> -> CString -> CInt -> IO CInt
-
--- struct zip *zip_open(const char *, int, int *);
-#ccall zip_open , CString -> CInt -> Ptr CInt -> IO (Ptr <zip>)
-
--- int zip_rename(struct zip *, int, const char *);
-#ccall zip_rename , Ptr <zip> -> CInt -> CString -> IO CInt
-
--- int zip_replace(struct zip *, int, struct zip_source *);
-#ccall zip_replace , Ptr <zip> -> CInt -> Ptr <zip_source> -> IO CInt
-
--- int zip_set_archive_comment(struct zip *, const char *, int);
-#ccall zip_set_archive_comment , Ptr <zip> -> CString -> CInt -> IO CInt
-
--- int zip_set_archive_flag(struct zip *, int, int);
-#ccall zip_set_archive_flag , Ptr <zip> -> CInt -> CInt -> IO CInt
-
--- int zip_set_file_comment(struct zip *, int, const char *, int);
-#ccall zip_set_file_comment , Ptr <zip> -> CInt -> CString -> CInt -> IO CInt
-
--- struct zip_source *zip_source_buffer(struct zip *, const void *, off_t, int);
-#ccall zip_source_buffer , Ptr <zip> -> Ptr () -> CSize -> CInt -> IO (Ptr <zip_source>)
-
--- struct zip_source *zip_source_file(struct zip *, const char *, off_t, off_t);
-#ccall zip_source_file , Ptr <zip> -> CString -> CSize -> CSize -> IO (Ptr <zip_source>)
-
--- struct zip_source *zip_source_filep(struct zip *, FILE *, off_t, off_t);
-#ccall zip_source_filep , Ptr <zip> -> Ptr CFile -> CSize -> CSize -> IO (Ptr <zip_source>)
-
--- void zip_source_free(struct zip_source *);
-#ccall zip_source_free , Ptr <zip_source> -> IO ()
-
--- struct zip_source *zip_source_function(struct zip *, zip_source_callback, void *);
-#ccall zip_source_function , Ptr <zip> -> <zip_source_callback> -> Ptr () -> IO (Ptr <zip_source>)
-
--- struct zip_source *zip_source_zip(struct zip *, struct zip *, int, int, off_t, off_t);
-#ccall zip_source_zip , Ptr <zip> -> Ptr <zip> -> CInt -> CInt -> CSize -> CSize -> IO (Ptr <zip_source>)
-
--- int zip_stat(struct zip *, const char *, int, struct zip_stat *);
-#ccall zip_stat , Ptr <zip> -> CString -> CInt -> Ptr <zip_stat> -> IO CInt
-
--- int zip_stat_index(struct zip *, int, int, struct zip_stat *);
-#ccall zip_stat_index , Ptr <zip> -> CInt -> CInt -> Ptr <zip_stat> -> IO CInt
-
--- void zip_stat_init(struct zip_stat *);
-#ccall zip_stat_init , Ptr <zip_stat> -> IO ()
-
--- const char *zip_strerror(struct zip *);
-#ccall zip_strerror , Ptr <zip> -> IO CString
-
--- int zip_unchange(struct zip *, int);
-#ccall zip_unchange , Ptr <zip> -> CInt -> IO CInt
-
--- int zip_unchange_all(struct zip *);
-#ccall zip_unchange_all , Ptr <zip> -> IO CInt
-
--- int zip_unchange_archive(struct zip *);
-#ccall zip_unchange_archive , Ptr <zip> -> IO CInt
-

Codec/Archive/LibZip/Types.hs

-{-# LANGUAGE DeriveDataTypeable#-}
-
-module Codec.Archive.LibZip.Types
-    ( Zip
-    , ZipFile
-    , ZipSource
-    , ZipStat(..)
-    , toZipStat
-    , OpenFlag(..)
-    , FileFlag(..)
-    , ZipError(..)
-    , ZipCompMethod(..)
-    , ZipEncryptionMethod(..)
-    , combine
-    ) where
-    
-import Data.Bits ((.|.))
-import Data.Time (UTCTime)
-import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
-import Data.Typeable (Typeable)
-import Data.Word (Word)
-import Foreign.C.String (peekCString)
-import Foreign.C.Types ()
-import Foreign.Ptr (Ptr, nullPtr)
-import qualified Control.Exception as E
-
-import Codec.Archive.LibZip.LowLevel
-
--- | Handler of the open zip file.
-type Zip = Ptr C'zip
-
--- | Handler of an open file in the zip archive.
-type ZipFile = Ptr C'zip_file
-
--- | Handler of data source for new files in the zip archive.
-type ZipSource = Ptr C'zip_source
-
--- |  File statistics expressed in native Haskell types.
-data ZipStat = ZipStat {
-      zs'name :: String
-    , zs'index :: Int
-    , zs'crc :: Word
-    , zs'mtime :: UTCTime
-    , zs'size :: Int
-    , zs'comp_size :: Int
-    , zs'comp_method :: ZipCompMethod
-    , zs'encryption_method :: ZipEncryptionMethod
-    } deriving (Show, Eq)
-
--- | Convert marshalled stat record.
-toZipStat :: C'zip_stat -> IO ZipStat
-toZipStat s = do
-    let np = c'zip_stat'name s
-    name <- if (np /= nullPtr) then peekCString np else return ""
-    let idx = fromIntegral $ c'zip_stat'index s
-    let crc = fromIntegral $ c'zip_stat'crc s
-    let mtime = posixSecondsToUTCTime . realToFrac $ c'zip_stat'mtime s
-    let size = fromIntegral $ c'zip_stat'size s
-    let comp_size = fromIntegral $ c'zip_stat'comp_size s
-    let comp_meth = toEnum . fromIntegral $ c'zip_stat'comp_method s
-    let enc_meth = toEnum . fromIntegral $ c'zip_stat'encryption_method s
-    return $ ZipStat name idx crc mtime size comp_size comp_meth enc_meth
-   
-
--- | Flags for opening an archive.
-data OpenFlag
-  = CreateFlag      -- ^ Create an archive if it does not exist.
-  | ExclFlag        -- ^ Error if the archive already exists.
-  | CheckConsFlag   -- ^ Check archive's consistency and error on failure.
-  deriving (Show,Eq)
-
-instance Enum OpenFlag where
-  fromEnum CheckConsFlag = c'ZIP_CHECKCONS
-  fromEnum CreateFlag = c'ZIP_CREATE
-  fromEnum ExclFlag = c'ZIP_EXCL
-  toEnum x | x == c'ZIP_CHECKCONS = CheckConsFlag
-  toEnum x | x == c'ZIP_CREATE = CreateFlag
-  toEnum x | x == c'ZIP_EXCL = ExclFlag
-  toEnum _ = undefined
-
--- | Flags for accessing files in the archive.
--- Please consult @libzip@ documentation about their use.
-data FileFlag
-  = FileNOCASE      -- ^ Ignore case on name lookup.
-  | FileNODIR       -- ^ Ignore directory component.
-  | FileCOMPRESSED  -- ^ Read the compressed data.
-  | FileUNCHANGED   -- ^ Read the original data, ignore changes.
-  | FileRECOMPRESS  -- ^ Force recompression of data.
-  deriving (Show,Eq)
-
-instance Enum FileFlag where
-  fromEnum FileCOMPRESSED = c'ZIP_FL_COMPRESSED
-  fromEnum FileNOCASE = c'ZIP_FL_NOCASE
-  fromEnum FileNODIR = c'ZIP_FL_NODIR
-  fromEnum FileRECOMPRESS = c'ZIP_FL_RECOMPRESS
-  fromEnum FileUNCHANGED = c'ZIP_FL_UNCHANGED
-  toEnum x | x == c'ZIP_FL_COMPRESSED = FileCOMPRESSED
-  toEnum x | x == c'ZIP_FL_NOCASE = FileNOCASE
-  toEnum x | x == c'ZIP_FL_NODIR = FileNODIR
-  toEnum x | x == c'ZIP_FL_RECOMPRESS = FileRECOMPRESS
-  toEnum x | x == c'ZIP_FL_UNCHANGED = FileUNCHANGED
-  toEnum _ = undefined
-
--- | @libzip@ error codes.
-data ZipError
-  = ErrOK             -- ^ No error.
-  | ErrMULTIDISK      -- ^ Multi-disk zip archives not supported.
-  | ErrRENAME         -- ^ Renaming temporary file failed.
-  | ErrCLOSE          -- ^ Closing zip archive failed.
-  | ErrSEEK           -- ^ Seek error.
-  | ErrREAD           -- ^ Read error.
-  | ErrWRITE          -- ^ Write error.
-  | ErrCRC            -- ^ CRC error.
-  | ErrZIPCLOSED      -- ^ Containing zip archive was closed.
-  | ErrNOENT          -- ^ No such file.
-  | ErrEXISTS         -- ^ File already exists.
-  | ErrOPEN           -- ^ Can't open file.
-  | ErrTMPOPEN        -- ^ Failure to create temporary file.
-  | ErrZLIB           -- ^ Zlib error.
-  | ErrMEMORY         -- ^ Malloc error.
-  | ErrCHANGED        -- ^ Entry has been changed.
-  | ErrCOMPNOTSUPP    -- ^ Compression method not supported.
-  | ErrEOF            -- ^ Premature EOF.
-  | ErrINVAL          -- ^ Invalid argument.
-  | ErrNOZIP          -- ^ Not a zip archive.
-  | ErrINTERNAL       -- ^ Internal error.
-  | ErrINCONS         -- ^ Zip archive inconsistent.
-  | ErrREMOVE         -- ^ Can't remove file.
-  | ErrDELETED        -- ^ Entry has been deleted.
-  deriving (Eq, Typeable)
-
-instance Enum ZipError where
-  fromEnum ErrCHANGED = c'ZIP_ER_CHANGED
-  fromEnum ErrCLOSE = c'ZIP_ER_CLOSE
-  fromEnum ErrCOMPNOTSUPP = c'ZIP_ER_COMPNOTSUPP
-  fromEnum ErrCRC = c'ZIP_ER_CRC
-  fromEnum ErrDELETED = c'ZIP_ER_DELETED
-  fromEnum ErrEOF = c'ZIP_ER_EOF
-  fromEnum ErrEXISTS = c'ZIP_ER_EXISTS
-  fromEnum ErrINCONS = c'ZIP_ER_INCONS
-  fromEnum ErrINTERNAL = c'ZIP_ER_INTERNAL
-  fromEnum ErrINVAL = c'ZIP_ER_INVAL
-  fromEnum ErrMEMORY = c'ZIP_ER_MEMORY
-  fromEnum ErrMULTIDISK = c'ZIP_ER_MULTIDISK
-  fromEnum ErrNOENT = c'ZIP_ER_NOENT
-  fromEnum ErrNOZIP = c'ZIP_ER_NOZIP
-  fromEnum ErrOK = c'ZIP_ER_OK
-  fromEnum ErrOPEN = c'ZIP_ER_OPEN
-  fromEnum ErrREAD = c'ZIP_ER_READ
-  fromEnum ErrREMOVE = c'ZIP_ER_REMOVE
-  fromEnum ErrRENAME = c'ZIP_ER_RENAME
-  fromEnum ErrSEEK = c'ZIP_ER_SEEK
-  fromEnum ErrTMPOPEN = c'ZIP_ER_TMPOPEN
-  fromEnum ErrWRITE = c'ZIP_ER_WRITE
-  fromEnum ErrZIPCLOSED = c'ZIP_ER_ZIPCLOSED
-  fromEnum ErrZLIB = c'ZIP_ER_ZLIB
-  toEnum x | x == c'ZIP_ER_CHANGED = ErrCHANGED
-  toEnum x | x == c'ZIP_ER_CLOSE = ErrCLOSE
-  toEnum x | x == c'ZIP_ER_COMPNOTSUPP = ErrCOMPNOTSUPP
-  toEnum x | x == c'ZIP_ER_CRC = ErrCRC
-  toEnum x | x == c'ZIP_ER_DELETED = ErrDELETED
-  toEnum x | x == c'ZIP_ER_EOF = ErrEOF
-  toEnum x | x == c'ZIP_ER_EXISTS = ErrEXISTS
-  toEnum x | x == c'ZIP_ER_INCONS = ErrINCONS
-  toEnum x | x == c'ZIP_ER_INTERNAL = ErrINTERNAL
-  toEnum x | x == c'ZIP_ER_INVAL = ErrINVAL
-  toEnum x | x == c'ZIP_ER_MEMORY = ErrMEMORY
-  toEnum x | x == c'ZIP_ER_MULTIDISK = ErrMULTIDISK
-  toEnum x | x == c'ZIP_ER_NOENT = ErrNOENT
-  toEnum x | x == c'ZIP_ER_NOZIP = ErrNOZIP
-  toEnum x | x == c'ZIP_ER_OK = ErrOK
-  toEnum x | x == c'ZIP_ER_OPEN = ErrOPEN
-  toEnum x | x == c'ZIP_ER_READ = ErrREAD
-  toEnum x | x == c'ZIP_ER_REMOVE = ErrREMOVE
-  toEnum x | x == c'ZIP_ER_RENAME = ErrRENAME
-  toEnum x | x == c'ZIP_ER_SEEK = ErrSEEK
-  toEnum x | x == c'ZIP_ER_TMPOPEN = ErrTMPOPEN
-  toEnum x | x == c'ZIP_ER_WRITE = ErrWRITE
-  toEnum x | x == c'ZIP_ER_ZIPCLOSED = ErrZIPCLOSED
-  toEnum x | x == c'ZIP_ER_ZLIB = ErrZLIB
-  toEnum _ = undefined
-
-instance E.Exception ZipError
-
-instance Show ZipError where
-  show ErrOK             =  "No error"
-  show ErrMULTIDISK      =  "Multi-disk zip archives not supported"
-  show ErrRENAME         =  "Renaming temporary file failed"
-  show ErrCLOSE          =  "Closing zip archive failed"
-  show ErrSEEK           =  "Seek error"
-  show ErrREAD           =  "Read error"
-  show ErrWRITE          =  "Write error"
-  show ErrCRC            =  "CRC error"
-  show ErrZIPCLOSED      =  "Containing zip archive was closed"
-  show ErrNOENT          =  "No such file"
-  show ErrEXISTS         =  "File already exists"
-  show ErrOPEN           =  "Can't open file"
-  show ErrTMPOPEN        =  "Failure to create temporary file"
-  show ErrZLIB           =  "Zlib error"
-  show ErrMEMORY         =  "Malloc failure"
-  show ErrCHANGED        =  "Entry has been changed"
-  show ErrCOMPNOTSUPP    =  "Compression method not supported"
-  show ErrEOF            =  "Premature EOF"
-  show ErrINVAL          =  "Invalid argument"
-  show ErrNOZIP          =  "Not a zip archive"
-  show ErrINTERNAL       =  "Internal error"
-  show ErrINCONS         =  "Zip archive inconsistent"
-  show ErrREMOVE         =  "Can't remove file"
-  show ErrDELETED        =  "Entry has been deleted"
-
--- | Compression methods.
-data ZipCompMethod
-  = CompDEFAULT         -- ^ Better of deflate or store.
-  | CompSTORE           -- ^ Stored (uncompressed).
-  | CompSHRINK          -- ^ Shrunk.
-  | CompREDUCE_1        -- ^ Reduced with factor 1
-  | CompREDUCE_2        -- ^ Reduced with factor 2
-  | CompREDUCE_3        -- ^ Reduced with factor 3
-  | CompREDUCE_4        -- ^ Reduced with factor 4
-  | CompIMPLODE         -- ^ Imploded.
-  | CompDEFLATE         -- ^ Deflated.
-  | CompDEFLATE64       -- ^ Deflate64.
-  | CompPKWARE_IMPLODE  -- ^ PKWARE imploding.
-  | CompBZIP2           -- ^ Compressed using BZIP2 algorithm.
-  | CompLZMA            -- ^ LZMA (EFS)
-  | CompTERSE           -- ^ Compressed using IBM TERSE (new).
-  | CompLZ77            -- ^ IBM LZ77 z Architecture (PFS).
-  | CompWAVPACK         -- ^ WavPack compressed data.
-  | CompPPMD            -- ^ PPMd version I, Rev 1.
-  deriving (Show, Eq)
-
-instance Enum ZipCompMethod where
-  fromEnum CompDEFAULT = c'ZIP_CM_DEFAULT
-  fromEnum CompSTORE = c'ZIP_CM_STORE
-  fromEnum CompSHRINK = c'ZIP_CM_SHRINK
-  fromEnum CompREDUCE_1 = c'ZIP_CM_REDUCE_1
-  fromEnum CompREDUCE_2 = c'ZIP_CM_REDUCE_2
-  fromEnum CompREDUCE_3 = c'ZIP_CM_REDUCE_3
-  fromEnum CompREDUCE_4 = c'ZIP_CM_REDUCE_4
-  fromEnum CompIMPLODE = c'ZIP_CM_IMPLODE
-  fromEnum CompDEFLATE = c'ZIP_CM_DEFLATE
-  fromEnum CompDEFLATE64 = c'ZIP_CM_DEFLATE64
-  fromEnum CompPKWARE_IMPLODE = c'ZIP_CM_PKWARE_IMPLODE
-  fromEnum CompBZIP2 = c'ZIP_CM_BZIP2
-  fromEnum CompLZMA = c'ZIP_CM_LZMA
-  fromEnum CompTERSE = c'ZIP_CM_TERSE
-  fromEnum CompLZ77 = c'ZIP_CM_LZ77
-  fromEnum CompWAVPACK = c'ZIP_CM_WAVPACK
-  fromEnum CompPPMD = c'ZIP_CM_PPMD
-  toEnum x | x == c'ZIP_CM_DEFAULT = CompDEFAULT
-  toEnum x | x == c'ZIP_CM_STORE = CompSTORE
-  toEnum x | x == c'ZIP_CM_SHRINK = CompSHRINK
-  toEnum x | x == c'ZIP_CM_REDUCE_1 = CompREDUCE_1
-  toEnum x | x == c'ZIP_CM_REDUCE_2 = CompREDUCE_2
-  toEnum x | x == c'ZIP_CM_REDUCE_3 = CompREDUCE_3
-  toEnum x | x == c'ZIP_CM_REDUCE_4 = CompREDUCE_4
-  toEnum x | x == c'ZIP_CM_IMPLODE = CompIMPLODE
-  toEnum x | x == c'ZIP_CM_DEFLATE = CompDEFLATE
-  toEnum x | x == c'ZIP_CM_DEFLATE64 = CompDEFLATE64
-  toEnum x | x == c'ZIP_CM_PKWARE_IMPLODE = CompPKWARE_IMPLODE
-  toEnum x | x == c'ZIP_CM_BZIP2 = CompBZIP2
-  toEnum x | x == c'ZIP_CM_LZMA = CompLZMA
-  toEnum x | x == c'ZIP_CM_TERSE = CompTERSE
-  toEnum x | x == c'ZIP_CM_LZ77 = CompLZ77
-  toEnum x | x == c'ZIP_CM_WAVPACK = CompWAVPACK
-  toEnum x | x == c'ZIP_CM_PPMD = CompPPMD
-  toEnum _ = undefined
-
--- | Encryption methods.
-data ZipEncryptionMethod
-  = EncryptNONE          -- ^ Not encrypted.
-  | EncryptTRAD_PKWARE   -- ^ Traditional PKWARE encryption.
-  | EncryptUNKNOWN       -- ^ Unknown algorithm.
-  deriving (Show,Eq)
-
-instance Enum ZipEncryptionMethod where
-  fromEnum EncryptNONE = c'ZIP_EM_NONE
-  fromEnum EncryptTRAD_PKWARE = c'ZIP_EM_TRAD_PKWARE
-  fromEnum EncryptUNKNOWN = c'ZIP_EM_UNKNOWN
-  toEnum x | x == c'ZIP_EM_NONE = EncryptNONE
-  toEnum x | x == c'ZIP_EM_TRAD_PKWARE = EncryptTRAD_PKWARE
-  toEnum x | x == c'ZIP_EM_UNKNOWN = EncryptUNKNOWN
-  toEnum _ = undefined
-
--- | 
-
-combine :: (Enum a, Num b) => [a] -> b
-combine fs = fromIntegral . foldr (.|.) 0 $ map fromEnum fs
-

LICENSE

-Copyright (c) 2009, 2010, Sergey Astanin
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-    * Neither the name of the Sergey Astanin nor the names of other
-      contributors may be used to endorse or promote products derived from this
-      software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

LibZip.cabal

-Name:          LibZip
-Version:       0.1.0
-License:       BSD3
-License-File:  LICENSE
-Author:        Sergey Astanin
-Maintainer:    Sergey Astanin <s.astanin@gmail.com>
-Homepage:      http://bitbucket.org/jetxee/hs-libzip/
-Bug-reports:   http://bitbucket.org/jetxee/hs-libzip/issues/
-
-Category:      Codec, Foreign
-Synopsis:      Bindings to libzip, a library for manipulating zip archives.
-Description:
-  libzip is a C library for reading, creating, and modifying zip archives.
-  This package allows to use it from Haskell code.
-
-Build-Type:     Simple
-Cabal-Version:  >= 1.2.3
-Tested-With:    GHC == 6.12.1
-
-Extra-Source-Files:
-    examples/legacyUnzip.hs, examples/hzip.hs
-  , runTests.hs, Tests/Common.hs, Tests/LegacyTests.hs
-  , Tests/MonadicTests.hs, Tests/test.zip
-
-Library
-  Extensions:
-      ForeignFunctionInterface
-  Exposed-Modules:
-      Codec.Archive.LibZip
-      Codec.Archive.LibZip.LowLevel
-      Codec.Archive.LibZip.Types
-      Codec.Archive.LibZip.LegacyZeroZero
-  Other-Modules:
-      Codec.Archive.LibZip.Errors
-  Build-Depends:
-      base >= 4.0 && < 4.3
-    , bindings-DSL >= 1.0 && < 1.1
-    , bytestring
-    , filepath
-    , time
-    , mtl
-  PkgConfig-Depends:
-      libzip >= 0.9
-  GHC-Options:
-      -Wall
-
-

LibZip/Codec/Archive/LibZip.hs

+{- | Monadic interface to @libzip@.
+
+Most of the operations on zip archive happen within 'Archive' monad
+(see 'withArchive').
+Partial reading of the files in the archive may be performed from
+within 'Entry' monad (see 'fromFile').
+Both 'Archive' and 'Entry' are monad transformers over 'IO', and allow
+for IO with single and double 'lift'ing respectingly.
+
+Note: LibZip does not handle text encodings. Even if its API accepts
+'String's (e.g. in 'sourceBuffer'), character codes above 255 should
+not be used.  The user is responsible of proper encoding the text
+data.
+
+/Examples/
+
+List files in the zip archive:
+
+@
+import System.Environment (getArgs)
+import Codec.Archive.LibZip
+
+main = do
+  (zipfile:_) <- getArgs
+  files <- withArchive [] zipfile $ fileNames []
+  mapM_ putStrLn files
+@
+
+Create a zip archive and a add file to the archive:
+
+@
+import System.Environment (getArgs)
+import Codec.Archive.LibZip
+
+main = do
+  (zipfile:_) <- getArgs
+  withArchive [CreateFlag] zipfile $ do
+     zs <- sourceBuffer \"Hello World!\"
+     addFile \"hello.txt\" zs
+@
+
+Extract and print a file from the zip archive:
+
+@
+import System.Environment (getArgs)
+import Codec.Archive.LibZip
+
+main = do
+  (zipfile:file:_) <- getArgs
+  bytes <- withArchive [] zipfile $ fileContents [] file
+  putStrLn bytes
+@
+
+See also an implementation of a simple zip archiver @hzip.hs@ in the
+@examples/@ directory of the source distribution.
+
+-}
+module Codec.Archive.LibZip
+    (
+    -- * Types
+      Archive
+    , Entry
+    , ZipStat(..)
+    -- * Archive operations
+    , withArchive, getZip
+    , numFiles, fileName, nameLocate, fileNames
+    , fileSize, fileSizeIx
+    , fileStat, fileStatIx
+    , deleteFile, deleteFileIx
+    , renameFile, renameFileIx
+    , addFile, addDirectory
+    , replaceFile, replaceFileIx
+    , sourceBuffer, sourceFile, sourceZip
+    , PureSource(..), sourcePure
+    , getComment, setComment, removeComment
+    , getFileComment, getFileCommentIx
+    , setFileComment, setFileCommentIx
+    , removeFileComment, removeFileCommentIx
+    , unchangeFile, unchangeFileIx
+    , unchangeArchive, unchangeAll
+    -- * File reading operations
+    , fromFile, fromFileIx
+    , readBytes, skipBytes, readContents
+    , fileContents, fileContentsIx
+    -- * Flags and options
+    , OpenFlag(..)
+    , FileFlag(..)
+    , ZipCompMethod(..)
+    , ZipEncryptionMethod(..)
+    -- * Exception handling
+    , ZipError(..)
+    , catchZipError
+    -- * Re-exports
+    , lift
+    ) where
+
+import Bindings.LibZip
+import Codec.Archive.LibZip.Types
+import Codec.Archive.LibZip.Errors
+
+import Data.Time.Clock (UTCTime, getCurrentTime)
+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
+import Data.Word (Word8)
+import Control.Monad.State.Strict
+    (StateT(..), MonadState(..), MonadTrans(..), lift, liftM)
+import Foreign.C.Error (Errno(..), eINVAL)
+import Foreign.C.String (withCString, withCStringLen, peekCString)
+import Foreign.C.Types (CInt, CSize)
+import Foreign.Marshal.Alloc (alloca)
+import Foreign.Marshal.Array (allocaArray, peekArray, withArrayLen, pokeArray)
+import Foreign.Marshal.Utils (with)
+import Foreign.Ptr (Ptr, nullPtr, castPtr)
+import Foreign.Storable (Storable, peek, poke, pokeElemOff, sizeOf)
+import qualified Control.Exception as E
+
+--
+-- Types
+--
+
+-- | Monadic computation with a zip archive. See 'withArchive'.
+type Archive a = StateT Zip IO a
+
+-- | Monadic computation to read from open archive entries.
+-- See 'fromFile' and 'fromFileIx'.
+type Entry a = StateT
+    (ZipFile,Int,[FileFlag])   -- (file, position index, access flags)
+    (StateT Zip IO)            -- archive monad
+    a
+
+--
+-- Archive operations
+--
+
+-- | Top-level wrapper for operations with an open
+-- archive. 'withArchive' opens and closes the file
+-- automatically. On error it throws 'ZipError'.
+withArchive :: [OpenFlag]  -- ^ Checks for consistency or existence.
+            -> FilePath    -- ^ Filename of the zip archive.
+            -> Archive a   -- ^ Action to do with the archive.
+            -> IO a
+withArchive flags path action =
+  withCString path $ \path' ->
+  alloca $ \errp ->
+  c'zip_open path' (combine flags) errp >>= \z ->
+  if z == nullPtr
+    then peek errp >>= E.throwIO. errFromCInt
+    else do
+      r <- fst `liftM` runStateT action z
+      e <- c'zip_close z
+      if e /= 0
+        then get_error z >>= E.throwIO
+        else return r
+
+-- | Get the number of entries in the archive.
+numFiles :: Archive Int
+numFiles = do
+  z <- getZip
+  lift $ fromIntegral `liftM` c'zip_get_num_files z
+
+-- | Get name of an entry in the archive by its index.
+fileName :: [FileFlag]  -- ^ 'FileUNCHANGED' flag can be used.
+         -> Int         -- ^ Position index of a file in the archive.
+         -> Archive FilePath  -- ^ Name of the file in the archive.
+fileName flags i = do
+  z <- getZip
+  lift $ do
+    n <- c'zip_get_name z (fromIntegral i) (combine flags)
+    doIf' (n /= nullPtr) z $ peekCString n
+
+-- | Locate an entry (get its index) in the archive by its name.
+nameLocate :: [FileFlag]  -- ^ Filename lookup mode.
+           -> FilePath    -- ^ Name of the file in the archive.
+           -> Archive (Maybe Int)  -- ^ 'Just' position index if found.
+nameLocate flags name = do
+  z <- getZip
+  lift $
+    withCString name $ \name' -> do
+    i <- fromIntegral `liftM` c'zip_name_locate z name' (combine flags)
+    if i < 0
+       then return Nothing
+       else return (Just i)
+
+-- | Get names of all entries (files and directories) in the archive.
+fileNames :: [FileFlag]  -- ^ 'FileUNCHANGED' flag is accepted.
+          -> Archive [FilePath]
+fileNames flags = do
+  n <- numFiles
+  mapM (fileName flags) [0..n-1]
+
+-- | Get size of a file in the archive.
+fileSize :: [FileFlag]  -- ^ Filename lookup mode, 'FileUNCHANGED' can be used.
+         -> FilePath    -- ^ Name of the file in the archive.
+         -> Archive Int -- ^ File size.
+fileSize flags name = fileStat flags name >>= return . zs'size
+
+-- | Get size of a file in the archive (by index).
+fileSizeIx :: [FileFlag]  -- ^ 'FileUNCHANGED' is accepted.
+           -> Int         -- ^ Position index of a file in the archive.
+           -> Archive Int -- ^ File size.
+fileSizeIx flags i = fileStatIx flags i >>= return . zs'size
+
+-- | Get information about a file in the archive.
+fileStat :: [FileFlag]  -- ^ Filename lookup mode, 'FileUNCHANGED' can be used.
+         -> FilePath    -- ^ Name of the file in the archive.
+         -> Archive ZipStat  -- ^ Infomation about the file.
+fileStat flags name = do
+  z <- getZip
+  lift $
+       withCString name $ \name' ->
+       alloca $ \stat -> do
+       c'zip_stat_init stat
+       r <- c'zip_stat z name' (combine flags) stat
+       doIf' (r == 0) z $ toZipStat =<< peek stat
+
+-- | Get information about a file in the archive (by index).
+fileStatIx :: [FileFlag]  -- ^ 'FileUNCHANGED' can be used.
+           -> Int         -- ^ Position index of a file in the archive.
+           -> Archive ZipStat  -- ^ Information about the file.
+fileStatIx flags i = do
+  z <- getZip
+  lift $
+       alloca $ \stat -> do
+       r <- c'zip_stat_index z (fromIntegral i) (combine flags) stat
+       doIf' (r == 0) z $ toZipStat =<< peek stat
+
+-- | Delete file from the archive.
+deleteFile :: [FileFlag]  -- ^ Filename lookup mode.
+           -> FilePath    -- ^ Filename.
+           -> Archive ()
+deleteFile flags name = do
+  mbi <- nameLocate flags name
+  maybe (lift $ E.throwIO ErrNOENT) deleteFileIx mbi
+
+-- | Delete file (referenced by position index) from the archive.
+deleteFileIx :: Int  -- ^ Position index of a file in the archive.
+             -> Archive ()
+deleteFileIx i = do
+  z <- getZip
+  r <- lift $ c'zip_delete z (fromIntegral i)
+  if r == 0
+     then return ()
+     else lift $ get_error z >>= E.throwIO
+
+-- | Rename file in the archive.
+renameFile :: [FileFlag]  -- ^ Filename lookup mode.
+           -> FilePath    -- ^ Old name.
+           -> FilePath    -- ^ New name.
+           -> Archive ()
+renameFile flags oldname newname = do
+  mbi <- nameLocate flags oldname
+  maybe (lift $ E.throwIO ErrNOENT) (\i -> renameFileIx i newname) mbi
+
+-- | Rename file (referenced by position index) in the archive.
+renameFileIx :: Int  -- ^ Position index of a file in the archive.
+             -> FilePath -- ^ New name.
+             -> Archive ()
+renameFileIx i newname = do
+  z <- getZip
+  r <- lift $ withCString newname $ c'zip_rename z (fromIntegral i)
+  if r == 0
+     then return ()
+     else lift $ get_error z >>= E.throwIO
+
+-- | Add a file to the archive.
+addFile :: FilePath   -- ^ Name of the file to create.
+        -> ZipSource  -- ^ Source where file data is obtained from.
+        -> Archive Int  -- ^ Position index of the new file.
+addFile name src = do
+  z <- getZip
+  lift $ withCString name $ \name' -> do
+    i <- c'zip_add z name' src
+    if i < 0
+       then c'zip_source_free src >> get_error z >>= E.throwIO
+       else return $ fromIntegral i
+
+-- | Add a directory to the archive.
+addDirectory :: FilePath     -- ^ Directory's name in the archive.
+             -> Archive Int  -- ^ Position index of the new directory entry.
+addDirectory name = do
+  z <- getZip
+  r <- lift $ withCString name $ c'zip_add_dir z
+  if r < 0
+     then lift $ get_error z >>= E.throwIO
+     else return (fromIntegral r)
+
+-- | Replace a file in the archive.
+replaceFile :: [FileFlag]  -- ^ Filename lookup mode.
+            -> FilePath    -- ^ File to replace.
+            -> ZipSource   -- ^ Source where the new file data is obtained from.
+            -> Archive ()
+replaceFile flags name src = do
+  mbi <- nameLocate flags name
+  maybe (lift $ c'zip_source_free src >> E.throwIO ErrNOENT)
+        (\i -> replaceFileIx i src >> return ()) mbi
+
+-- | Replace a file in the archive (referenced by position index).
+replaceFileIx :: Int       -- ^ Position index of a file in the archive.
+              -> ZipSource -- ^ Source where the new file data is obtained from
+              -> Archive ()
+replaceFileIx i src = do
+  z <- getZip
+  lift $ do
+    r <- c'zip_replace z (fromIntegral i) src
+    if r < 0
+       then c'zip_source_free src >> get_error z >>= E.throwIO
+       else return ()
+
+-- | Create a data source. Note: input is converted to @[Word8]@ internally.
+sourceBuffer :: (Enum a)
+             => [a]
+             -> Archive ZipSource
+sourceBuffer src = do
+  let ws = map (toEnum . fromEnum) src :: [Word8]
+  z <- getZip
+  lift $ withArrayLen ws $ \len buf -> do
+      zs <- c'zip_source_buffer z (castPtr buf) (fromIntegral len) 0
+      if zs == nullPtr
+         then get_error z >>= E.throwIO
+         else return zs
+
+-- | Create a data source from a file.
+sourceFile :: FilePath   -- ^ File to open.
+           -> Int        -- ^ Offset from the beginning of the file.
+           -> Int        -- ^ The number of bytes to read. If @0@ or @-1@,
+                         -- the read till the end of file.
+           -> Archive ZipSource
+sourceFile name offset len = do
+  z <- getZip
+  lift $ withCString name $ \name' -> do
+      zs <- c'zip_source_file z name' (fromIntegral offset) (fromIntegral len)
+      if zs == nullPtr
+         then get_error z >>= E.throwIO
+         else return zs
+
+-- | Create a data source from a file in the zip archive.
+sourceZip :: [FileFlag]  -- ^ 'FileUNCHANGED' and 'FileRECOMPRESS' can be used.
+          -> Zip         -- ^ Source archive.
+          -> Int         -- ^ Position index of a file in the source archive.
+          -> Int         -- ^ Offset from the beginning of the file.
+          -> Int         -- ^ The number of bytes to read. If @0@ or @-1@,
+                         -- then read till the end of file.
+          -> Archive ZipSource
+sourceZip flags srcz srcidx offset len = do
+  z <- getZip
+  lift $ do
+    zs <- c'zip_source_zip z srcz (fromIntegral srcidx)
+          (combine flags) (fromIntegral offset) (fromIntegral len)
+    if zs == nullPtr
+       then get_error z >>= E.throwIO
+       else return zs
+
+-- | Create a data source from a 'PureSource'.
+-- Note: input of @[a]@ is converted to @[Word8]@ internally.
+sourcePure :: (Enum a, Storable a, Storable st)
+           => PureSource a st -> Archive ZipSource
+sourcePure pureSrc = do
+  z <- getZip
+  lift $ do
+    cb <- mk'zip_source_callback (runPureSource pureSrc)
+    zs <- with (srcState pureSrc) $
+          \pState -> c'zip_source_function z cb (castPtr pState)
+    if zs == nullPtr
+       then get_error z >>= E.throwIO
+       else return zs
+
+-- | Wrapper for a user-provided pure function to be used with 'sourcePure'.
+-- Data size should be known in advance ('srcSize').
+-- The function should support reading by chunks ('readSrc').
+data (Enum a, Storable a, Storable st) => PureSource a st = PureSource {
+    srcState :: st   -- ^ Initial state of the source.
+  , srcSize  :: Int  -- ^ Total size of the data.
+  , srcMTime :: Maybe UTCTime  -- ^ Modification time (current time if Nothing).
+  , readSrc  :: Int -> st -> Maybe (Int, [a], st) -- ^ Read a chunk of the data,
+                -- return @Just@ the size of data read, the data themselves and
+                -- the new state of the source, or @Nothing@ on error.
+  }
+
+runPureSource :: (Enum a, Storable a, Storable st)
+              => PureSource a st
+              -> (Ptr () -> Ptr () -> CSize -> C'zip_source_cmd -> IO CSize)
+runPureSource src pState pData len cmd
+  | cmd == c'ZIP_SOURCE_OPEN = return 0
+  | cmd == c'ZIP_SOURCE_READ = do
+      state <- peek (castPtr pState :: Ptr st)
+      case readSrc (src { srcState = state }) (fromIntegral len) state of
+        Just (len',bs,state') -> do
+          pokeArray (castPtr pData :: Ptr Word8) (map (toEnum.fromEnum) bs)
+          poke (castPtr pState) state'
+          return (fromIntegral len')
+        Nothing -> return (-1)
+  | cmd == c'ZIP_SOURCE_CLOSE = return 0
+  | cmd == c'ZIP_SOURCE_STAT = do
+      t <- maybe getCurrentTime return (srcMTime src)
+      let pt = fromInteger . round . utcTimeToPOSIXSeconds $ t
+      let pStat = castPtr pData
+      c'zip_stat_init pStat
+      stat <- peek pStat
+      let stat' = stat { c'zip_stat'mtime = pt
+                       , c'zip_stat'size = fromIntegral $ srcSize src }
+      poke pStat stat'
+      return $ fromIntegral (sizeOf stat')
+  | cmd == c'ZIP_SOURCE_ERROR = do
+      let pErrs = castPtr pData :: Ptr CInt
+      poke pErrs (fromIntegral . fromEnum $ ErrINVAL)
+      let (Errno esys) = eINVAL
+      pokeElemOff pErrs 1 esys
+      return $ fromIntegral (2 * sizeOf esys)
+  | cmd == c'ZIP_SOURCE_FREE = return 0
+  | otherwise = return (-1)
+        
+-- | Get zip archive comment.
+getComment :: [FileFlag]  -- ^ 'FileUNCHANGED' can be used.
+           -> Archive (Maybe String)
+getComment flags = do
+  z <- getZip
+  (c,n) <- lift $ alloca $ \lenp -> do
+         c <- c'zip_get_archive_comment z lenp (combine flags)
+         n <- peek lenp
+         return (c,n)
+  if  c == nullPtr
+    then return Nothing
+    else lift $ peekCString c >>= return . Just . take (fromIntegral n)
+
+-- | Set zip archive comment.
+setComment :: String   -- ^ Comment message.
+           -> Archive ()
+setComment msg = do
+  z <- getZip
+  r <- lift $ withCStringLen msg $ \(msg',i') ->
+       c'zip_set_archive_comment z msg' (fromIntegral i')
+  if r < 0
+     then lift $ get_error z >>= E.throwIO
+     else return ()
+
+-- | Remove zip archive comment.
+removeComment :: Archive ()
+removeComment = do
+  z <- getZip
+  r <- lift $ c'zip_set_archive_comment z nullPtr 0
+  if r < 0
+     then lift $ get_error z >>= E.throwIO
+     else return ()
+
+-- | Get comment for a file in the archive.
+getFileComment :: [FileFlag]  -- ^ Filename lookup mode.
+               -> FilePath    -- ^ Filename
+               -> Archive (Maybe String)
+getFileComment flags name = do
+  mbi <- nameLocate flags name
+  maybe (lift $ E.throwIO ErrNOENT) (getFileCommentIx flags) mbi
+
+-- | Get comment for a file in the archive (referenced by position index).
+getFileCommentIx :: [FileFlag]  -- ^ FileUNCHANGED can be used.
+                 -> Int         -- ^ Position index of the file.
+                 -> Archive (Maybe String)
+getFileCommentIx flags i = do
+  z <- getZip
+  (c,n) <- lift $ alloca $ \lenp -> do
+           c <- c'zip_get_file_comment z (fromIntegral i) lenp (combine flags)
+           n <- peek lenp
+           return (c,n)
+  if c == nullPtr
+     then return Nothing
+     else lift $ peekCString c >>= return . Just . take (fromIntegral n)
+
+-- | Set comment for a file in the archive.
+setFileComment :: [FileFlag]   -- ^ Name lookup mode.
+               -> FilePath     -- ^ Filename.
+               -> String       -- ^ New file comment.
+               -> Archive ()
+setFileComment flags path comment = do
+  mbi <- nameLocate flags path
+  maybe (lift $ E.throwIO ErrNOENT) (flip setFileCommentIx comment) mbi
+
+-- | Set comment for a file in the archive (referenced by position index).
+setFileCommentIx :: Int        -- ^ Position index of a file in the archive.
+                 -> String     -- ^ New file comment. 
+                 -> Archive ()
+setFileCommentIx i comment = do
+  z <- getZip
+  r <- lift $ withCStringLen comment $ \(msg,len) ->
+       c'zip_set_file_comment z (fromIntegral i) msg (fromIntegral len)
+  if r < 0
+     then lift $ get_error z >>= E.throwIO
+     else return ()
+
+-- | Remove comment for a file in the archive.
+removeFileComment :: [FileFlag]  -- ^ Filename lookup mode.
+                  -> FilePath    -- ^ Filename.
+                  -> Archive ()
+removeFileComment flags path = do
+  mbi <- nameLocate flags path
+  maybe (lift $ E.throwIO ErrNOENT) removeFileCommentIx mbi
+
+-- | Remove comment for a file in the archive (referenced by position index).
+removeFileCommentIx :: Int  -- ^ Position index of a file in the archive.
+                    -> Archive ()
+removeFileCommentIx i = do
+  z <- getZip
+  r <- lift $ c'zip_set_file_comment z (fromIntegral i) nullPtr 0
+  if r < 0
+     then lift $ get_error z >>= E.throwIO
+     else return ()
+
+-- | Undo changes to a file in the archive.
+unchangeFile :: [FileFlag]  -- ^ Filename lookup mode.
+             -> FilePath    -- ^ Filename.
+             -> Archive ()
+unchangeFile flags name = do
+  mbi <- nameLocate flags name
+  maybe (lift $ E.throw ErrNOENT) unchangeFileIx mbi
+
+-- | Undo changes to a file in the archive (referenced by position index).
+unchangeFileIx :: Int  -- ^ Position index of a file in the archive.
+               -> Archive ()
+unchangeFileIx i = do
+  z <- getZip
+  lift $ do
+    r <- c'zip_unchange z (fromIntegral i)
+    if r < 0
+       then get_error z >>= E.throwIO
+       else return ()
+
+-- | Undo global changes to zip archive (revert changes to the archive
+-- comment and global flags).
+unchangeArchive :: Archive ()
+unchangeArchive = do
+  z <- getZip
+  lift $ do
+    r <- c'zip_unchange_archive z
+    if r < 0
+       then get_error z >>= E.throwIO
+       else return ()
+
+-- | Undo all changes in a zip archive.
+unchangeAll :: Archive ()
+unchangeAll = do