LibZip-0.0.1: 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.
module Codec.Archive.LibZip
(-- * Types
Zip,ZipFile,OpenFlag(..),FileFlag(..),ZipError(..)
-- * High-level Haskell functions
,withZip,getFiles,getFileSize,readZipFile
-- * 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', ...
{# 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 #}
-- | 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
-- 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.
readZipFile :: Zip -- ^ zip archive
-> String -- ^ name of the file in the archive
-> [FileFlag] -- ^ file name mode
-> IO B.ByteString
readZipFile z fname flags = do
sz <- getFileSize z fname flags
f <- fopen z fname flags
bytes <- fread f sz
fclose f
return $ B.pack bytes
-- 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 [] = Nothing
lastMay xs = Just $ last xs
-- vim: set expandtab ts=2 sw=2 ai si: