Commits

Sergey Astanin  committed 3c46755 Merge

Merged 0.1 into default.

  • Participants
  • Parent commits a1b9b0d, 73ed42e

Comments (0)

Files changed (18)

 syntax: glob
 *.swp
 *~
-Codec/Archive/*.{chs.c,chi,hs,hi,o,h}
+*.{chs.c,chi,hs,hi,o,h,tix}
 dist
+.hpc

File Codec/Archive/LibZip.chs

-{-# LANGUAGE ForeignFunctionInterface #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-
--- | Partial Haskell bindings to @libzip@ library.
--- These bindings provide read-only access to zip archives.
--- Produced with @c2hs@ for @libzip@ ver. 0.9.
---
--- API is not stabilized yet, especially high-level part. If you use it, please
--- require the same major version (e.g. >= 0.0.3 && < 0.1).
--- Feedback and suggestions are welcome.
-module Codec.Archive.LibZip
-  (-- * Types
-   Zip,ZipFile,OpenFlag(..),FileFlag(..),ZipError(..)
-   ,Word8
-   -- * High-level interface
-   -- These functions are intended to reduce boiler-plate. They acquire and
-   -- release relevant resources automatically (open and close archives, files).
-   ,withZip,getFiles,getFileSize
-   ,readZipFile,readZipFile'
-   ,readZipFileHead,readZipFileHead'
-   -- * Low-level direct bindings to @libzip@ functions
-   -- These functions correspond to their C namesakes with @zip_@ prefix.
-   -- Please refer to @libzip@ documentation to learn about their semantics.
-   ,open,close,get_num_files,get_name
-   ,fopen,fopen_index,fclose,fread
-   -- * Utilities
-   ,catchZipError,isFile,isDir
-  ) where
-
-import C2HS
-
-import qualified Control.Exception as E
-import qualified Data.ByteString as B
-import Control.Applicative ((<$>))
-import Data.Typeable
-
-#include "zip.h"
-
-{# context lib="zip" prefix="zip_" #}
-
--- | Flags for 'withZip'.
-{# enum define OpenFlag
-  { ZIP_CREATE as CreateFlag
-  , ZIP_EXCL as ExclFlag
-  , ZIP_CHECKCONS as CheckConsFlag
-  } deriving (Show,Eq) #}
-
--- | Flags for 'getFiles', 'getFileSize', 'readZipFile', ...
--- Please consult @libzip@ documentation about their use.
-{# enum define FileFlag
-  { ZIP_FL_NOCASE as FileNOCASE
-  , ZIP_FL_NODIR as FileNODIR
-  , ZIP_FL_COMPRESSED as FileCOMPRESSED
-  , ZIP_FL_UNCHANGED as FileUNCHANGED
-  , ZIP_FL_RECOMPRESS as FileRECOMPRESS
-  } deriving (Show,Eq) #}
-
--- | @libzip@ error codes.
-{# enum define ZipError
-  { ZIP_ER_OK as ErrOK
-  , ZIP_ER_MULTIDISK as ErrMULTIDISK
-  , ZIP_ER_RENAME as ErrRENAME
-  , ZIP_ER_CLOSE as ErrCLOSE
-  , ZIP_ER_SEEK as ErrSEEK
-  , ZIP_ER_READ as ErrREAD
-  , ZIP_ER_WRITE as ErrWRITE
-  , ZIP_ER_CRC as ErrCRC
-  , ZIP_ER_ZIPCLOSED as ErrZIPCLOSED
-  , ZIP_ER_NOENT as ErrNOENT
-  , ZIP_ER_EXISTS as ErrEXISTS
-  , ZIP_ER_OPEN as ErrOPEN
-  , ZIP_ER_TMPOPEN as ErrTMPOPEN
-  , ZIP_ER_ZLIB as ErrZLIB
-  , ZIP_ER_MEMORY as ErrMEMORY
-  , ZIP_ER_CHANGED as ErrCHANGED
-  , ZIP_ER_COMPNOTSUPP as ErrCOMPNOTSUPP
-  , ZIP_ER_EOF as ErrEOF
-  , ZIP_ER_INVAL as ErrINVAL
-  , ZIP_ER_NOZIP as ErrNOZIP
-  , ZIP_ER_INTERNAL as ErrINTERNAL
-  , ZIP_ER_INCONS as ErrINCONS
-  , ZIP_ER_REMOVE as ErrREMOVE
-  , ZIP_ER_DELETED as ErrDELETED
-  } deriving (Eq,Typeable) #}
-
-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"
-
--- | Handler of the open zip file.
-{# pointer *zip as Zip newtype #}
-
-instance Show Zip where
-  show z@(Zip p)
-    | isOpen z  = "Zip[open:" ++ (show p) ++ "]"
-    | otherwise = "Zip[closed]"
-
-isOpen :: Zip -> Bool
-isOpen (Zip p) = p /= nullPtr
-
--- | Handler for an open file in the zip archive.
-{# pointer *zip_file as ZipFile newtype #}
-
-instance Show ZipFile where
-  show (ZipFile p)
-    | p /= nullPtr = "ZipFile[open:" ++ (show p) ++ "]"
-    | otherwise    = "ZipFile[closed]"
-
-#c
-typedef struct zip_stat *zip_stat_t;
-#endc
-
-{-
-{# enum define CompMethod
-   { ZIP_CM_DEFAULT as CompDEFAULT
-   , ZIP_CM_STORE as CompSTORE
-   , ZIP_CM_SHRINK as CompSHRINK
-   , ZIP_CM_REDUCE_1 as CompREDUCE_1
-   , ZIP_CM_REDUCE_2 as CompREDUCE_2
-   , ZIP_CM_REDUCE_3 as CompREDUCE_3
-   , ZIP_CM_REDUCE_4 as CompREDUCE_4
-   , ZIP_CM_IMPLODE as CompIMPLODE
-   , ZIP_CM_DEFLATE as CompDEFLATE
-   , ZIP_CM_DEFLATE64 as CompDEFLATE64
-   , ZIP_CM_PKWARE_IMPLODE as CompPKWARE_IMPLODE
-   , ZIP_CM_BZIP2 as CompBZIP2
-   , ZIP_CM_LZMA as CompLZMA
-   , ZIP_CM_TERSE as CompTERSE
-   , ZIP_CM_LZ77 as CompLZ77
-   , ZIP_CM_WAVPACK as CompWAVPACK
-   , ZIP_CM_PPMD as CompPPMD
-   } deriving (Show,Eq) #}
-
-{# enum define EncryptMethod
-   { ZIP_EM_NONE as EncryptNONE
-   , ZIP_EM_TRAD_PKWARE as EncryptTRAD_PKWARE
-   , ZIP_EM_UNKNOWN as EncryptUNKNOWN
-   } deriving (Show,Eq) #}
--}
-
--- struct zip *zip_open(const char *, int, int *);
-{# fun open as open_
-   { `String'
-   , combineBitMasks `[OpenFlag]'
-   , alloca- `ZipError' peekError*
-   } -> `Zip' id #}
-   where peekError i = peek i >>= return . toEnum . cIntConv
-
--- | 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
-open path flags = do
-  (z,e) <- open_ path flags
-  if isOpen z
-    then return z
-    else E.throwIO e
-
--- int zip_close(struct zip *);
-{# fun close as close_
-   { id `Zip'
-   } -> `Int' #}
-
--- | Close zip archive.
-close :: Zip -> IO ()
-close z = do
-  r <- close_ z
-  if r == 0
-    then return ()
-    else E.throwIO ErrINTERNAL -- FIXME
-
--- int zip_get_num_files(struct zip *);
---
--- | Return the number of files in the archive.
-{# fun get_num_files
-   { id `Zip'
-   } -> `Int' errorOnNegative* #}
-  where errorOnNegative i | i < 0 = E.throwIO ErrZIPCLOSED
-        errorOnNegative i = return $ cIntConv i
-
--- const char *zip_get_name(struct zip *, int, int);
---
--- | Get name of file by index.
-{# fun get_name
-   { id `Zip'
-   , `Int'
-   , combineBitMasks `[FileFlag]'
-   } -> `String' errorOnNull* #}
-   where errorOnNull p | p /= nullPtr = peekCString p
-         errorOnNull _ | otherwise = E.throwIO ErrINVAL -- FIXME
-
--- struct zip_file *zip_fopen(struct zip *, const char *, int);
-{# fun zip_fopen as fopen_
-   { id `Zip'
-   , `String'
-   , combineBitMasks `[FileFlag]'
-   } -> `ZipFile' id #}
-
--- struct zip_file *zip_fopen_index(struct zip *archive, int index, int flags);
-{# fun zip_fopen_index as fopen_index_
-   { id `Zip'
-   , `Int'
-   , combineBitMasks `[FileFlag]'
-   } -> `ZipFile' id #}
-
--- | Open file in zip archive for reading.
-fopen :: Zip -> String -> [FileFlag] -> IO ZipFile
-fopen z fn flags = do
-  zf@(ZipFile ptr) <- fopen_ z fn flags
-  if ptr == nullPtr
-    then E.throwIO ErrNOENT -- FIXME
-    else return zf
-
--- | Open n-th file in zip archive for reading.
-fopen_index :: Zip -> Int -> [FileFlag] -> IO ZipFile
-fopen_index z n flags = do
-  zf@(ZipFile ptr) <- fopen_index_ z n flags
-  if ptr == nullPtr
-    then E.throwIO ErrNOENT -- FIXME
-    else return zf
-
--- int zip_fclose(struct zip_file *);
---
--- | Close file in zip archive.
-{# fun zip_fclose as fclose
-   { id `ZipFile'
-   } -> `()' errorOrNothing* #}
-   where errorOrNothing 0 = return ()
-         errorOrNothing _ = E.throwIO ErrINVAL -- FIXME
-
--- ssize_t zip_fread(struct zip_file *, void *, size_t);
---
--- | Read from file in zip archive.
-fread :: ZipFile -> Int -> IO [Word8]
-fread zf count = do
-  allocaBuf $ \ptr -> do
-     rcount <- {#call zip_fread#} zf ptr (cIntConv count)
-     if rcount < 0
-       then E.throwIO ErrREAD
-       else peekBuf ptr >>= return . take (cIntConv rcount)
-  where
-    -- Nasty pointer casts to deal with void*
-    allocaBuf :: (Ptr () -> IO [Word8]) -> IO [Word8]
-    allocaBuf f =
-        let alloc = allocaArray count :: (Ptr Word8 -> IO [Word8]) -> IO [Word8]
-            castFun :: (Ptr () -> IO [a]) -> (Ptr a -> IO [a])
-            castFun fun = \ptrb -> let ptra = castPtr ptrb in fun ptra
-        in  alloc $ castFun f
-    peekBuf :: Ptr () -> IO [Word8]
-    peekBuf p = peekArray count $ castPtr p
-
--- 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' ->
-  allocaBytes {# sizeof zip_stat_t #} $ \st -> do
-    {# call zip_stat_init #} st
-    ret <- {# call zip_stat #} z name' (combineBitMasks flags) st
-    if ret /= 0
-      then E.throwIO ErrINTERNAL -- FIXME
-      else fromIntegral <$> {# get zip_stat_t->size #} st
-
--- | 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
-
--- Helpers
-
--- | 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
-
--- | 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
-
--- vim: set expandtab ts=2 sw=2 ai si:

File Codec/Archive/LibZip.hs

+{- | Monadic interface to @libzip@.
+
+Most of the operations on zip archive happen within 'Archive' monad
+(see 'withArchive'). Operations which require a file in the archive
+to be open are performed 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.
+
+Example. Reading a byte range from a file in the archive:
+
+@
+import System.Environment (getArgs)
+import Codec.Archive.LibZip
+
+main = do
+  (zipfile:innerfile:_) <- getArgs
+  withArchive [] zipfile $
+    fromFile [] innerfile $ do
+      skipBytes 10
+      next10 <- readBytes 10
+      lift . lift $ putStrLn $ \"Bytes from 11 to 20: \" ++ next10
+@
+-}
+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)

File 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
+

File 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

File 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
+

File 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
+

File LibZip.cabal

 Name:          LibZip
-Version:       0.0.2
+Version:       0.1.0
 License:       BSD3
 License-File:  LICENSE
 Author:        Sergey Astanin
 Bug-reports:   http://bitbucket.org/jetxee/hs-libzip/issues/
 
 Category:      Codec, Foreign
-Synopsis:      Partial bindings to libzip to read zip archives.
+Synopsis:      Bindings to libzip, a library for manipulating zip archives.
 Description:
-  This package provides basic read-only access to zip-archives.
-  It is produced with @c2hs@ for @libzip@ ver. 0.9.
-Exposed-modules: Codec.Archive.LibZip
-Other-modules:   C2HS
+  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
-Extra-Tmp-Files:
-                Codec/Archive/LibZip.hs
-              , Codec/Archive/LibZip.chs.h
-Build-Tools:    c2hs
-Build-Depends:	base >= 4.0 && < 4.3
-              , bytestring
-              , haskell98
-GHC-Options:    -Wall
-Extensions:     ForeignFunctionInterface
-              , FlexibleContexts
-              , DeriveDataTypeable
-              , CPP
+Tested-With:    GHC == 6.12.1
 
-Tested-with:   GHC == 6.10.4
+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
 
+
 > import Distribution.PackageDescription (emptyHookedBuildInfo)
 >
 > main = defaultMainWithHooks simpleUserHooks
->   { preBuild = generateC2HSdoths
->   , runTests = runUnitTests
+>   { runTests = runUnitTests
 >   }
 >
-> generateC2HSdoths _ _ =
->   system "c2hs -l" >>=
->   onExit "Cannot copy C2HS.hs library module in." emptyHookedBuildInfo
 >
 > runUnitTests _ _ _ _ =
->   system "cd tests && runhaskell -lzip Tests.hs" >>=
->   onExit "Some tests did not pass." ()
+>   system "runhaskell -lzip runTests.hs" >>=
+>   onExit "\nSome tests did not pass." ()
 >
 > onExit :: String -> a -> ExitCode -> IO a
 > onExit errmsg okvalue r =

File Tests/Common.hs

+module Tests.Common
+    ( testzip, testfiles, lastfile, lastfilesize
+    , world_txt
+    , toUpper, toLower, map2
+    ) where
+
+import Data.Char (toUpper, toLower)
+
+testzip = "Tests/test.zip"
+testfiles = [ "hello/", "hello/world.txt" ]
+lastfile = last testfiles
+lastfilesize = 71 :: Int
+
+world_txt = "And God saw everything that he had made,\
+            \ and behold, it was very good.\n"
+
+-- map odd positions with f, even positions with g
+map2 :: (a -> b) -> (a -> b) -> [a] -> [b]
+map2 f g [] = []
+map2 f g (x:xs) = (f x):map2 g f xs

File Tests/LegacyTests.hs

+module Tests.LegacyTests
+    ( legacyTests
+    ) where
+
+import Codec.Archive.LibZip.LegacyZeroZero
+
+import Tests.Common
+
+import qualified Data.ByteString as B
+import Test.HUnit
+
+legacyTests = TestList
+  [ "read list of files" ~: do
+      files <- withZip testzip [] $ \z -> getFiles z [] :: IO [String]
+      files @?= testfiles
+  , "read file size" ~: do
+      sz <- withZip testzip [] $ \z -> getFileSize z lastfile []
+      sz @?= lastfilesize
+  , "case-insensitive file names" ~: do
+    sz <- withZip testzip [] $ \z ->
+          getFileSize z (map2 toUpper toLower $ lastfile) [FileNOCASE]
+    sz @?= lastfilesize
+  , "open error if exists (with ExclFlag)" ~: do
+    err <- catchZipError
+            (withZip testzip [ExclFlag] $ \_ -> return ErrOK)
+            (return . id)
+    err @?= ErrEXISTS
+  , "read file" ~: do
+    txt <- withZip testzip [] $ \z -> readZipFile z lastfile []
+    txt @?= toByteString world_txt
+  , "open file by index" ~: do
+    txt <- withZip testzip [] $ \z -> do
+        f <- fopen_index z 1 [] -- index 0 is of the parent dir
+        bytes <- fread f (length world_txt)
+        return $ B.pack bytes
+    txt @?= toByteString world_txt
+  ]
+
+
+toByteString :: String -> B.ByteString
+toByteString s = B.pack $ map (fromIntegral . fromEnum) s
+

File Tests/MonadicTests.hs

+{-# LANGUAGE FlexibleInstances #-}
+module Tests.MonadicTests where
+
+import Codec.Archive.LibZip
+import Tests.Common
+
+import Data.Int (Int64)
+import Foreign.Storable
+import Foreign.Ptr (Ptr, castPtr)
+
+import System.Directory (doesFileExist, getTemporaryDirectory, removeFile)
+import System.FilePath ((</>))
+import Test.HUnit
+import qualified Control.Exception as E
+
+monadicTests = TestList
+  [ "read list of files" ~: do
+      files <- withArchive [] testzip $ fileNames []
+      files @?= testfiles
+  , "read file size" ~: do
+      sz <- withArchive [] testzip $ fileSize [] lastfile
+      sz @?= lastfilesize
+  , "case-insensitive file names" ~: do
+      sz <- withArchive [] testzip $
+              fileSize [FileNOCASE] (map2 toUpper toLower $ lastfile)
+      sz @?= lastfilesize
+  , "open error if exists (with ExclFlag)" ~: do
+      err <- catchZipError
+             (withArchive [ExclFlag] testzip $ lift $ E.throwIO ErrOK)
+             (return . id)
+      err @?= ErrEXISTS
+  , "open error if archive does not exists" ~: do
+      err <- catchZipError
+             (withArchive [] "notexists.zip" $ return ErrOK)
+             (return . id)
+      err @?= ErrOPEN
+  , "read file" ~: do
+      txt <- withArchive [] testzip $ fileContents [] lastfile
+      txt @?= world_txt
+  , "read file by index" ~: do
+      txt <- withArchive [] testzip $ fileContentsIx [] (length testfiles - 1)
+      txt @?= world_txt
+  , "skipBytes/readBytes" ~: do
+      txt <- withArchive [] testzip $
+               fromFile [] lastfile $ do
+                  skipBytes 13
+                  readBytes 10
+      txt @?= (take 10 . drop 13 $ world_txt)
+  , "create an archive/use sourceBuffer" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_sourceBuffer.zip"
+      i <- withArchive [CreateFlag] tmpzip $ do
+           addDirectory "hello"
+           addFile "hello/world.txt" =<< sourceBuffer world_txt
+      tmpzip `doesExistAnd` \f -> do
+           txt <- withArchive [] f $ fileContents [] "hello/world.txt"
+           removeFile f
+           (txt, i) @?= (world_txt, 1)
+  , "create an archive/use sourceFile" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_sourceFile.zip"
+      tmpsrc <- getTmpFileName "test_LibZip_sourceFile.txt"
+      writeFile tmpsrc world_txt
+      withArchive [CreateFlag] tmpzip $
+           addFile "world.txt" =<< sourceFile tmpsrc 0 0
+      tmpzip `doesExistAnd` \f -> do
+           txt <- withArchive [] f $ fileContents [] "world.txt"
+           removeFile tmpzip
+           removeFile tmpsrc
+           txt @?= world_txt
+  , "create an archive/use sourceZip" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_sourceZip.zip"
+      withArchive [] testzip $ do
+           zsrc <- getZip
+           lift $ withArchive [CreateFlag] tmpzip $
+               addFile "world.txt" =<< sourceZip [] zsrc 1 0 0
+      tmpzip `doesExistAnd` \f -> do
+           txt <- withArchive [] f $ fileContents [] "world.txt"
+           removeFile tmpzip
+           txt @?= world_txt
+  , "create an archive/use sourcePure" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_sourcePure.zip"
+      let src = PureSource
+           { srcState = (0, length world_txt) -- needs a Storable instance
+           , srcSize = length world_txt
+           , srcMTime = Nothing
+           , readSrc = \len (pos,lft) ->
+                       let n = min len lft
+                           buf = take n . drop pos $ world_txt
+                       in  Just (n, buf, (pos+n,lft-n))
+           }
+      withArchive [CreateFlag] tmpzip $ do
+           addFile "world.txt" =<< sourcePure src
+      tmpzip `doesExistAnd` \f -> do
+           txt <- withArchive [] f $ fileContents [] "world.txt"
+           removeFile tmpzip
+           txt @?= world_txt
+  , "delete a file" ~: do
+      let orig = [("one", "one"), ("two", "two")]
+      let final = init orig
+      tmpzip <- getTmpFileName "test_LibZip_delete.zip"
+      mkArchive tmpzip orig
+      fs_orig <- withArchive [] tmpzip $ fileNames []
+      withArchive [] tmpzip $ deleteFile [] "two"
+      fs_final <- withArchive [] tmpzip $ fileNames []
+      removeFile tmpzip
+      (fs_orig, fs_final) @?= (map fst orig, map fst final)
+  , "attempt to delete a non-existing file" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_delete_ne.zip"
+      mkArchive tmpzip [("world.txt", world_txt)]
+      r1 <- catchZipError
+            (withArchive [] tmpzip $
+                         deleteFile [] "doesnotexist" >> return ErrOK)
+            (return . id)
+      r2 <- catchZipError
+            (withArchive [] tmpzip $
+                         deleteFileIx 100 >> return ErrOK)
+            (return . id)
+      removeFile tmpzip
+      (r1, r2) @?= (ErrNOENT, ErrINVAL)
+  , "rename a file" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_rename.zip"
+      mkArchive tmpzip [("world.txt", world_txt)]
+      fs <- withArchive [] tmpzip $ do
+              renameFile [] "world.txt" "hello.txt"
+              fileNames []
+      removeFile tmpzip
+      fs @?= ["hello.txt"]
+  , "attempt to rename a non-existing file" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_rename_ne.zip"
+      mkArchive tmpzip [("world.txt", world_txt)]
+      r <- catchZipError
+            (withArchive [] tmpzip $ do
+              renameFile [] "doesnotexist" "hello.txt"
+              return ErrOK)
+            (return . id)
+      removeFile tmpzip
+      r @?= ErrNOENT
+  , "attempt to rename to an empty name" ~: do
+      tmpzip <- getTmpFileName "test_LibZip_rename_inval.zip"
+      mkArchive tmpzip [("world.txt", world_txt)]
+      r <- catchZipError
+             (withArchive [] tmpzip $ do
+              renameFile [] "world.txt" ""
+              return ErrOK)
+             (return . id)
+      removeFile tmpzip
+      r @?= ErrINVAL