zip-0.1.2: Operations on zip archives

Copyright© 2016 Mark Karpov
LicenseBSD 3 clause
MaintainerMark Karpov <markkarpov@openmailbox.org>
Stabilityexperimental
Portabilityportable
Safe HaskellNone
LanguageHaskell2010

Codec.Archive.Zip

Contents

Description

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

First, we use EntrySelector type that can be obtained from Path Rel File paths. This method may seem awkward at first, but it will protect you from problems with portability when your archive is unpacked on a different platform. Using of well-typed paths is also something you should consider doing in your projects anyway. Even if you don't want to use Path module in your project, it's easy to marshal FilePath to Path just before using functions from the library.

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 collection of pending actions that can be turned into 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 update, commit function is your friend. There are even “undo” functions, by the way.

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 smart constructor mkEntrySelector, and it's the only “key” that can be used to select files in archive or to name new files.

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 it is recommended in the specification). On the other hand, in can be rendered as ordinary relative file path in OS-specific format, when needed.

mkEntrySelector :: MonadThrow m => Path Rel File -> m EntrySelector Source

Create EntrySelector from Path Rel File. To avoid problems with distribution of the archive, characters that some operating systems do not expect in paths are not allowed. Proper paths should pass these checks:

  • isValid
  • isValid
  • binary representation of normalized path should be not longer than 65535 bytes

This function can throw EntrySelectorException exception.

unEntrySelector :: EntrySelector -> Path Rel File Source

Make a relative path from EntrySelector. Every EntrySelector produces single Path Rel File that corresponds to it.

getEntryName :: EntrySelector -> Text Source

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

data EntrySelectorException Source

Exception describing various troubles you can have with EntrySelector.

Constructors

InvalidEntrySelector (Path Rel File)

Selector cannot be created from this path

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 representation can be built given this date structure and actual archive contents.

Constructors

EntryDescription 

Fields

edVersionMadeBy :: Version

Version made by

edVersionNeeded :: Version

Version needed to extract

edCompression :: CompressionMethod

Compression method

edModTime :: UTCTime

Last modification date and time

edCRC32 :: Word32

CRC32 check sum

edCompressedSize :: Natural

Size of compressed entry

edUncompressedSize :: Natural

Size of uncompressed entry

edOffset :: Natural

Absolute offset of local file header

edComment :: Maybe Text

Entry comment

edExtraField :: Map Word16 ByteString

All extra fields found

data CompressionMethod Source

Supported compression methods.

Constructors

Store

Store file uncompressed

Deflate

Deflate

BZip2

Compressed using BZip2 algorithm

Archive description

data ArchiveDescription Source

Information about archive as a whole.

Constructors

ArchiveDescription 

Fields

adComment :: Maybe Text

Comment of entire archive

adCDOffset :: Natural

Absolute offset of start of central directory

adCDSize :: Natural

Size of central directory record

Exceptions

data ZipException Source

Bad things that can happen when you use the library.

Constructors

EntryDoesNotExist (Path Abs File) EntrySelector

Thrown when you try to get contents of non-existing entry

ParsingFailed (Path Abs File) String

Thrown when archive structure cannot be parsed

Archive monad

data ZipArchive a Source

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

createArchive Source

Arguments

:: (MonadIO m, MonadCatch m) 
=> Path b File

Location of archive file to create

-> ZipArchive a

Actions that form archive's content

-> m a 

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

withArchive Source

Arguments

:: (MonadIO m, MonadThrow m) 
=> Path b File

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 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 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 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 archive. Do not hesitate to use the function frequently: scanning of archive happens only once anyway.

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

doesEntryExist :: EntrySelector -> ZipArchive Bool Source

Check whether 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 specific archive entry as 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.

sourceEntry Source

Arguments

:: EntrySelector

Selector that identifies archive entry

-> Sink ByteString (ResourceT IO) a

Sink where to stream entry contents

-> ZipArchive a

Contents of the entry (if found)

Stream contents of archive entry to specified Sink.

Throws: EntryDoesNotExist.

saveEntry Source

Arguments

:: EntrySelector

Selector that identifies archive entry

-> Path b File

Where to save the file

-> ZipArchive () 

Save 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 value read from archive. The function returns True when the check sums are the same — that is, data is not corrupted.

Throws: EntryDoesNotExist.

unpackInto :: Path b Dir -> ZipArchive () Source

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

getArchiveComment :: ZipArchive (Maybe Text) Source

Get archive comment.

getArchiveDescription :: ZipArchive ArchiveDescription Source

Get 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 archive given its contents in binary form.

sinkEntry Source

Arguments

:: CompressionMethod

Compression method to use

-> Source (ResourceT IO) ByteString

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

-> (Path Abs File -> ZipArchive EntrySelector)

How to get EntrySelector

-> Path b File

Path to file to add

-> ZipArchive () 

Load entry from given file.

copyEntry Source

Arguments

:: Path b File

Path to archive to copy from

-> EntrySelector

Name of entry (in source archive) to copy

-> EntrySelector

Name of entry to insert (in actual archive)

-> ZipArchive () 

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

packDirRecur Source

Arguments

:: CompressionMethod

Compression method to use

-> (Path Abs File -> ZipArchive EntrySelector)

How to get EntrySelector

-> Path b Dir

Path to directory to add

-> ZipArchive () 

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

The action can throw the same exceptions as listDirRecur and InvalidEntrySelector.

Modifying entries

renameEntry Source

Arguments

:: EntrySelector

Original entry name

-> EntrySelector

New entry name

-> ZipArchive () 

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

deleteEntry :: EntrySelector -> ZipArchive () Source

Delete entry from 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 upon

-> ZipArchive () 

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

deleteEntryComment :: EntrySelector -> ZipArchive () Source

Delete 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 “last modification” date/time. Specified entry may be missing, in that case this 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. 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). 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 archive.

Operations on archive as a whole

setArchiveComment :: Text -> ZipArchive () Source

Set comment of entire archive.

deleteArchiveComment :: ZipArchive () Source

Delete archive comment if it's present.

Control over editing

undoEntryChanges :: EntrySelector -> ZipArchive () Source

Undo changes to specific archive entry.

undoArchiveChanges :: ZipArchive () Source

Undo changes to 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 archive in one pass. The actions are committed automatically when program leaves the realm of ZipArchive monad (i.e. as part of createArchive or withArchive), or can be forced explicitly with help of this function. Once committed, changes take place in the file system and cannot be undone.