zip-1.1.0: Operations on zip archives

Copyright© 2016–2018 Mark Karpov
LicenseBSD 3 clause
MaintainerMark Karpov <markkarpov92@gmail.com>
Stabilityexperimental
Portabilityportable
Safe HaskellNone
LanguageHaskell2010

Codec.Archive.Zip

Contents

Description

The module provides everything you may need to manipulate Zip archives. There are three things that should be clarified right away, to avoid confusion in the future.

First, we use the EntrySelector type that can be obtained from relative FilePaths (paths to directories are not allowed). This method may seem awkward at first, but it will protect you from the problems with portability when your archive is unpacked on a different platform.

The second thing, that is rather a consequence of the first, is that there is no way to add directories, or to be precise, empty directories to your archive. This approach is used in Git, and I find it quite sane.

Finally, the third feature of the library is that it does not modify archive instantly, because doing so on every manipulation would often be inefficient. Instead we maintain a collection of pending actions that can be turned into an optimized procedure that efficiently modifies archive in one pass. Normally this should be of no concern to you, because all actions are performed automatically when you leave the realm of ZipArchive monad. If, however, you ever need to force an update, the commit function is your friend. There are even “undo” functions, by the way.

Examples

An example of a program that prints a list of archive entries:

import Codec.Archive.Zip
import System.Environment (getArgs)
import qualified Data.Map as M

main :: IO ()
main = do
  [path]  <- getArgs
  entries <- withArchive path (M.keys <$> getEntries)
  mapM_ print entries

Create a Zip archive with a “Hello World” file:

import Codec.Archive.Zip
import System.Environment (getArgs)

main :: IO ()
main = do
  [path] <- getArgs
  s      <- mkEntrySelector "hello-world.txt"
  createArchive path (addEntry Store "Hello, World!" s)

Extract contents of a specific file and print them:

import Codec.Archive.Zip
import System.Environment (getArgs)
import qualified Data.ByteString.Char8 as B

main :: IO ()
main = do
  [path,f] <- getArgs
  s        <- mkEntrySelector f
  bs       <- withArchive path (getEntry s)
  B.putStrLn bs

Synopsis

Types

Entry selector

data EntrySelector Source #

This data type serves for naming and selection of archive entries. It can be created only with help of the smart constructor mkEntrySelector, and it's the only “key” that can be used to refer to files in archive or to name new archive entries.

The abstraction is crucial for ensuring that created archives are portable across operating systems, file systems, and different platforms. Since on some operating systems, file paths are case-insensitive, this selector is also case-insensitive. It makes sure that only relative paths are used to name files inside archive, as it's recommended in the specification. It also guarantees that forward slashes are used when the path is stored inside archive for compatibility with Unix-like operating systems (as recommended in the specification). On the other hand, in can be rendered as an ordinary relative file path in OS-specific format when needed.

mkEntrySelector :: MonadThrow m => FilePath -> m EntrySelector Source #

Create an EntrySelector from a FilePath. To avoid problems with distribution of the archive, characters that some operating systems do not expect in paths are not allowed.

Argument to mkEntrySelector should pass these checks:

  • isValid
  • isValid
  • it is a relative path without slash at the end
  • binary representations of normalized path should be not longer than 65535 bytes

This function can throw an EntrySelectorException.

unEntrySelector :: EntrySelector -> FilePath Source #

Restore a relative path from EntrySelector. Every EntrySelector corresponds to a single FilePath.

getEntryName :: EntrySelector -> Text Source #

Get an entry name in the from that is suitable for writing to file header, given an EntrySelector.

Entry description

data EntryDescription Source #

This record represents all information about archive entry that can be stored in a zip archive. It does not mirror local file header or central directory file header, but their binary representations can be built given this data structure and the actual archive contents.

Constructors

EntryDescription 

Fields

data CompressionMethod Source #

Supported compression methods.

Constructors

Store

Store file uncompressed

Deflate

Deflate

BZip2

Compressed using BZip2 algorithm

Instances

Bounded CompressionMethod Source # 
Enum CompressionMethod Source # 
Eq CompressionMethod Source # 
Data CompressionMethod Source # 

Methods

gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> CompressionMethod -> c CompressionMethod #

gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c CompressionMethod #

toConstr :: CompressionMethod -> Constr #

dataTypeOf :: CompressionMethod -> DataType #

dataCast1 :: Typeable (* -> *) t => (forall d. Data d => c (t d)) -> Maybe (c CompressionMethod) #

dataCast2 :: Typeable (* -> * -> *) t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c CompressionMethod) #

gmapT :: (forall b. Data b => b -> b) -> CompressionMethod -> CompressionMethod #

gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> CompressionMethod -> r #

gmapQr :: (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> CompressionMethod -> r #

gmapQ :: (forall d. Data d => d -> u) -> CompressionMethod -> [u] #

gmapQi :: Int -> (forall d. Data d => d -> u) -> CompressionMethod -> u #

gmapM :: Monad m => (forall d. Data d => d -> m d) -> CompressionMethod -> m CompressionMethod #

gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> CompressionMethod -> m CompressionMethod #

gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> CompressionMethod -> m CompressionMethod #

Ord CompressionMethod Source # 
Read CompressionMethod Source # 
Show CompressionMethod Source # 

Archive description

data ArchiveDescription Source #

Information about archive as a whole.

Constructors

ArchiveDescription 

Fields

Instances

Eq ArchiveDescription Source # 
Data ArchiveDescription Source # 

Methods

gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> ArchiveDescription -> c ArchiveDescription #

gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c ArchiveDescription #

toConstr :: ArchiveDescription -> Constr #

dataTypeOf :: ArchiveDescription -> DataType #

dataCast1 :: Typeable (* -> *) t => (forall d. Data d => c (t d)) -> Maybe (c ArchiveDescription) #

dataCast2 :: Typeable (* -> * -> *) t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c ArchiveDescription) #

gmapT :: (forall b. Data b => b -> b) -> ArchiveDescription -> ArchiveDescription #

gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> ArchiveDescription -> r #

gmapQr :: (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> ArchiveDescription -> r #

gmapQ :: (forall d. Data d => d -> u) -> ArchiveDescription -> [u] #

gmapQi :: Int -> (forall d. Data d => d -> u) -> ArchiveDescription -> u #

gmapM :: Monad m => (forall d. Data d => d -> m d) -> ArchiveDescription -> m ArchiveDescription #

gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> ArchiveDescription -> m ArchiveDescription #

gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> ArchiveDescription -> m ArchiveDescription #

Ord ArchiveDescription Source # 
Read ArchiveDescription Source # 
Show ArchiveDescription Source # 

Exceptions

Archive monad

data ZipArchive a Source #

Monad that provides context necessary for performing operations on zip archives. It's intentionally opaque and not a monad transformer to limit the actions that can be performed in it to those provided by this module and their combinations.

Instances

Monad ZipArchive Source # 

Methods

(>>=) :: ZipArchive a -> (a -> ZipArchive b) -> ZipArchive b #

(>>) :: ZipArchive a -> ZipArchive b -> ZipArchive b #

return :: a -> ZipArchive a #

fail :: String -> ZipArchive a #

Functor ZipArchive Source # 

Methods

fmap :: (a -> b) -> ZipArchive a -> ZipArchive b #

(<$) :: a -> ZipArchive b -> ZipArchive a #

Applicative ZipArchive Source # 

Methods

pure :: a -> ZipArchive a #

(<*>) :: ZipArchive (a -> b) -> ZipArchive a -> ZipArchive b #

liftA2 :: (a -> b -> c) -> ZipArchive a -> ZipArchive b -> ZipArchive c #

(*>) :: ZipArchive a -> ZipArchive b -> ZipArchive b #

(<*) :: ZipArchive a -> ZipArchive b -> ZipArchive a #

MonadIO ZipArchive Source # 

Methods

liftIO :: IO a -> ZipArchive a #

MonadThrow ZipArchive Source # 

Methods

throwM :: Exception e => e -> ZipArchive a #

MonadCatch ZipArchive Source # 

Methods

catch :: Exception e => ZipArchive a -> (e -> ZipArchive a) -> ZipArchive a #

MonadMask ZipArchive Source # 

Methods

mask :: ((forall a. ZipArchive a -> ZipArchive a) -> ZipArchive b) -> ZipArchive b #

uninterruptibleMask :: ((forall a. ZipArchive a -> ZipArchive a) -> ZipArchive b) -> ZipArchive b #

generalBracket :: ZipArchive a -> (a -> ExitCase b -> ZipArchive c) -> (a -> ZipArchive b) -> ZipArchive (b, c) #

MonadBase IO ZipArchive Source #

Since: 0.2.0

Methods

liftBase :: IO α -> ZipArchive α #

MonadBaseControl IO ZipArchive Source #

Since: 0.2.0

Associated Types

type StM (ZipArchive :: * -> *) a :: * #

type StM ZipArchive a Source # 
type StM ZipArchive a = (a, ZipState)

data ZipState Source #

Internal state record used by the ZipArchive monad. This is only exported for use with MonadBaseControl methods, you can't look inside.

Since: 0.2.0

createArchive Source #

Arguments

:: MonadIO m 
=> FilePath

Location of archive file to create

-> ZipArchive a

Actions that form archive's content

-> m a 

Create a new archive given its location and an action that describes how to create contents of the archive. This will silently overwrite the specified file if it already exists. See withArchive if you want to work with an existing archive.

withArchive Source #

Arguments

:: MonadIO m 
=> FilePath

Location of archive to work with

-> ZipArchive a

Actions on that archive

-> m a 

Work with an existing archive. See createArchive if you want to create a new archive instead.

This operation may fail with:

  • isAlreadyInUseError if the file is already open and cannot be reopened;
  • isDoesNotExistError if the file does not exist;
  • isPermissionError if the user does not have permission to open the file;
  • ParsingFailed when specified archive is something this library cannot parse (this includes multi-disk archives, for example).

Please note that entries with invalid (non-portable) file names may be missing in the list of entries. Files that are compressed with unsupported compression methods are skipped as well. Also, if several entries would collide on some operating systems (such as Windows, because of its case-insensitivity), only one of them will be available, because EntrySelector is case-insensitive. These are the consequences of the design decision to make it impossible to create non-portable archives with this library.

Retrieving information

getEntries :: ZipArchive (Map EntrySelector EntryDescription) Source #

Retrieve description of all archive entries. This is an efficient operation that can be used for example to list all entries in an archive. Do not hesitate to use the function frequently: scanning of archive happens only once anyway.

Please note that the returned value only reflects actual contents of the archive in file system, non-committed actions do not influence the list of entries, see commit for more information.

doesEntryExist :: EntrySelector -> ZipArchive Bool Source #

Check whether the specified entry exists in the archive. This is a simple shortcut defined as:

doesEntryExist s = M.member s <$> getEntries

getEntryDesc :: EntrySelector -> ZipArchive (Maybe EntryDescription) Source #

Get EntryDescription for specified entry. This is a simple shortcut defined as:

getEntryDesc s = M.lookup s <$> getEntries

getEntry Source #

Arguments

:: EntrySelector

Selector that identifies archive entry

-> ZipArchive ByteString

Contents of the entry

Get contents of a specific archive entry as a strict ByteString. It's not recommended to use this on big entries, because it will suck out a lot of memory. For big entries, use conduits: sourceEntry.

Throws: EntryDoesNotExist.

getEntrySource Source #

Arguments

:: (PrimMonad m, MonadThrow m, MonadResource m) 
=> EntrySelector

Selector that identifies archive entry

-> ZipArchive (ConduitT () ByteString m ()) 

Get an entry source.

Throws: EntryDoesNotExist.

Since: 0.1.3

sourceEntry Source #

Arguments

:: EntrySelector

Selector that identifies archive entry

-> ConduitT ByteString Void (ResourceT IO) a

Sink where to stream entry contents

-> ZipArchive a

Contents of the entry (if found)

Stream contents of an archive entry to the given Sink.

Throws: EntryDoesNotExist.

saveEntry Source #

Arguments

:: EntrySelector

Selector that identifies archive entry

-> FilePath

Where to save the file

-> ZipArchive () 

Save a specific archive entry as a file in the file system.

Throws: EntryDoesNotExist.

checkEntry Source #

Arguments

:: EntrySelector

Selector that identifies archive entry

-> ZipArchive Bool

Is the entry intact?

Calculate CRC32 check sum and compare it with the value read from the archive. The function returns True when the check sums are the same—that is, the data is not corrupted.

Throws: EntryDoesNotExist.

unpackInto :: FilePath -> ZipArchive () Source #

Unpack the entire archive into the specified directory. The directory will be created if it does not exist.

getArchiveComment :: ZipArchive (Maybe Text) Source #

Get the archive comment.

getArchiveDescription :: ZipArchive ArchiveDescription Source #

Get the archive description record.

Modifying archive

Adding entries

addEntry Source #

Arguments

:: CompressionMethod

Compression method to use

-> ByteString

Entry contents

-> EntrySelector

Name of entry to add

-> ZipArchive () 

Add a new entry to the archive given its contents in binary form.

sinkEntry Source #

Arguments

:: CompressionMethod

Compression method to use

-> ConduitT () ByteString (ResourceT IO) ()

Source of entry contents

-> EntrySelector

Name of entry to add

-> ZipArchive () 

Stream data from the specified source to an archive entry.

loadEntry Source #

Arguments

:: CompressionMethod

Compression method to use

-> EntrySelector

Name of entry to add

-> FilePath

Path to file to add

-> ZipArchive () 

Load an entry from a given file.

copyEntry Source #

Arguments

:: FilePath

Path to archive to copy from

-> EntrySelector

Name of entry (in source archive) to copy

-> EntrySelector

Name of entry to insert (in current archive)

-> ZipArchive () 

Copy an entry “as is” from another zip archive. If the entry does not exist in that archive, EntryDoesNotExist will be eventually thrown.

packDirRecur Source #

Arguments

:: CompressionMethod

Compression method to use

-> (FilePath -> ZipArchive EntrySelector)

How to get EntrySelector from a path relative to the root of the directory we pack

-> FilePath

Path to directory to add

-> ZipArchive () 

Add an entire directory to the archive. Please note that due to the design of the library, empty sub-directories won't be added.

The action can throw InvalidEntrySelector.

Modifying entries

renameEntry Source #

Arguments

:: EntrySelector

Original entry name

-> EntrySelector

New entry name

-> ZipArchive () 

Rename an entry in the archive. If the entry does not exist, nothing will happen.

deleteEntry :: EntrySelector -> ZipArchive () Source #

Delete an entry from the archive, if it does not exist, nothing will happen.

recompress Source #

Arguments

:: CompressionMethod

New compression method

-> EntrySelector

Name of entry to re-compress

-> ZipArchive () 

Change compression method of an entry, if it does not exist, nothing will happen.

setEntryComment Source #

Arguments

:: Text

Text of the comment

-> EntrySelector

Name of entry to comment on

-> ZipArchive () 

Set an entry comment, if that entry does not exist, nothing will happen. Note that if binary representation of the comment is longer than 65535 bytes, it will be truncated on writing.

deleteEntryComment :: EntrySelector -> ZipArchive () Source #

Delete an entry's comment, if that entry does not exist, nothing will happen.

setModTime Source #

Arguments

:: UTCTime

New modification time

-> EntrySelector

Name of entry to modify

-> ZipArchive () 

Set the “last modification” date/time. The specified entry may be missing, in that case the action has no effect.

addExtraField Source #

Arguments

:: Word16

Tag (header id) of extra field to add

-> ByteString

Body of the field

-> EntrySelector

Name of entry to modify

-> ZipArchive () 

Add an extra field. The specified entry may be missing, in that case this action has no effect.

deleteExtraField Source #

Arguments

:: Word16

Tag (header id) of extra field to delete

-> EntrySelector

Name of entry to modify

-> ZipArchive () 

Delete an extra field by its type (tag). The specified entry may be missing, in that case this action has no effect.

forEntries Source #

Arguments

:: (EntrySelector -> ZipArchive ())

Action to perform

-> ZipArchive () 

Perform an action on every entry in the archive.

Operations on archive as a whole

setArchiveComment :: Text -> ZipArchive () Source #

Set comment of the entire archive.

deleteArchiveComment :: ZipArchive () Source #

Delete the archive comment if it's present.

Control over editing

undoEntryChanges :: EntrySelector -> ZipArchive () Source #

Undo changes to a specific archive entry.

undoArchiveChanges :: ZipArchive () Source #

Undo changes to the archive as a whole (archive's comment).

undoAll :: ZipArchive () Source #

Undo all changes made in this editing session.

commit :: ZipArchive () Source #

Archive contents are not modified instantly, but instead changes are collected as “pending actions” that should be committed, in order to efficiently modify the archive in one pass. The actions are committed automatically when the program leaves the realm of ZipArchive monad (i.e. as part of createArchive or withArchive), or can be forced explicitly with the help of this function. Once committed, changes take place in the file system and cannot be undone.