mirror of
https://github.com/simplex-chat/zip.git
synced 2026-06-03 09:17:33 +00:00
Numerous cosmetic corrections to the docs
This commit is contained in:
+65
-62
@@ -7,16 +7,16 @@
|
||||
-- Stability : experimental
|
||||
-- Portability : portable
|
||||
--
|
||||
-- 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.
|
||||
-- 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 '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
|
||||
-- use the "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
|
||||
@@ -26,11 +26,12 @@
|
||||
-- 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
|
||||
-- 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 update, 'commit'
|
||||
-- function is your friend. There are even “undo” functions, by the way.
|
||||
-- 'ZipArchive' monad. If, however, you ever need to force update, the
|
||||
-- 'commit' function is your friend. There are even “undo” functions, by the
|
||||
-- way.
|
||||
--
|
||||
-- An example of a program that prints list of archive entries:
|
||||
--
|
||||
@@ -160,7 +161,7 @@ import qualified Data.Set as E
|
||||
----------------------------------------------------------------------------
|
||||
-- Archive monad
|
||||
|
||||
-- | Monad that provides context necessary for performing operations on
|
||||
-- | Monad that provides context necessary for performing operations on zip
|
||||
-- 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.
|
||||
@@ -188,10 +189,10 @@ data ZipState = ZipState
|
||||
-- ^ Pending actions
|
||||
}
|
||||
|
||||
-- | 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.
|
||||
-- | Create a new archive given its location and 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.
|
||||
|
||||
createArchive :: (MonadIO m, MonadCatch m)
|
||||
=> Path b File -- ^ Location of archive file to create
|
||||
@@ -209,7 +210,7 @@ createArchive path m = do
|
||||
liftIO (evalStateT action st)
|
||||
|
||||
-- | Work with an existing archive. See 'createArchive' if you want to
|
||||
-- create new archive instead.
|
||||
-- create a new archive instead.
|
||||
--
|
||||
-- This operation may fail with:
|
||||
--
|
||||
@@ -229,9 +230,9 @@ createArchive path m = do
|
||||
-- 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.
|
||||
-- 'EntrySelector' is case-insensitive. These are the consequences of the
|
||||
-- design decision to make it impossible to create non-portable archives
|
||||
-- with this library.
|
||||
|
||||
withArchive :: (MonadIO m, MonadThrow m)
|
||||
=> Path b File -- ^ Location of archive to work with
|
||||
@@ -256,15 +257,15 @@ withArchive path m = 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.
|
||||
-- Please note that the returned value only reflects actual contents of
|
||||
-- archive in file system, non-committed actions do not influence the list
|
||||
-- of entries, see 'commit' for more information.
|
||||
|
||||
getEntries :: ZipArchive (Map EntrySelector EntryDescription)
|
||||
getEntries = ZipArchive (gets zsEntries)
|
||||
|
||||
-- | Check whether specified entry exists in the archive. This is a simple
|
||||
-- shortcut defined as:
|
||||
-- | Check whether the specified entry exists in the archive. This is a
|
||||
-- simple shortcut defined as:
|
||||
--
|
||||
-- > doesEntryExist s = M.member s <$> getEntries
|
||||
|
||||
@@ -307,7 +308,7 @@ getEntrySource s = do
|
||||
Nothing -> throwM (EntryDoesNotExist path s)
|
||||
Just desc -> return (I.sourceEntry path desc True)
|
||||
|
||||
-- | Stream contents of archive entry to specified 'Sink'.
|
||||
-- | Stream contents of an archive entry to the given 'Sink'.
|
||||
--
|
||||
-- Throws: 'EntryDoesNotExist'.
|
||||
|
||||
@@ -322,7 +323,7 @@ sourceEntry s sink = do
|
||||
src <- getEntrySource s
|
||||
(liftIO . runResourceT) (src $$ sink)
|
||||
|
||||
-- | Save specific archive entry as a file in the file system.
|
||||
-- | Save a specific archive entry as a file in the file system.
|
||||
--
|
||||
-- Throws: 'EntryDoesNotExist'.
|
||||
|
||||
@@ -332,9 +333,9 @@ saveEntry
|
||||
-> ZipArchive ()
|
||||
saveEntry s path = sourceEntry s (CB.sinkFile (toFilePath path))
|
||||
|
||||
-- | Calculate CRC32 check sum and compare it with value read from
|
||||
-- | 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, data is not corrupted.
|
||||
-- that is, the data is not corrupted.
|
||||
--
|
||||
-- Throws: 'EntryDoesNotExist'.
|
||||
|
||||
@@ -348,8 +349,8 @@ checkEntry s = do
|
||||
-- 'sourceEntry' would have thrown 'EntryDoesNotExist' already.
|
||||
return (calculated == given)
|
||||
|
||||
-- | Unpack entire archive into specified directory. The directory will be
|
||||
-- created if it does not exist.
|
||||
-- | Unpack the entire archive into the specified directory. The directory
|
||||
-- will be created if it does not exist.
|
||||
|
||||
unpackInto :: Path b Dir -> ZipArchive ()
|
||||
unpackInto dir' = do
|
||||
@@ -362,12 +363,12 @@ unpackInto dir' = do
|
||||
forM_ selectors $ \s ->
|
||||
saveEntry s (dir </> unEntrySelector s)
|
||||
|
||||
-- | Get archive comment.
|
||||
-- | Get the archive comment.
|
||||
|
||||
getArchiveComment :: ZipArchive (Maybe Text)
|
||||
getArchiveComment = adComment <$> getArchiveDescription
|
||||
|
||||
-- | Get archive description record.
|
||||
-- | Get the archive description record.
|
||||
|
||||
getArchiveDescription :: ZipArchive ArchiveDescription
|
||||
getArchiveDescription = ZipArchive (gets zsArchive)
|
||||
@@ -375,7 +376,7 @@ getArchiveDescription = ZipArchive (gets zsArchive)
|
||||
----------------------------------------------------------------------------
|
||||
-- Modifying archive
|
||||
|
||||
-- | Add a new entry to archive given its contents in binary form.
|
||||
-- | Add a new entry to the archive given its contents in binary form.
|
||||
|
||||
addEntry
|
||||
:: CompressionMethod -- ^ Compression method to use
|
||||
@@ -393,7 +394,7 @@ sinkEntry
|
||||
-> ZipArchive ()
|
||||
sinkEntry t src s = addPending (I.SinkEntry t src s)
|
||||
|
||||
-- | Load entry from given file.
|
||||
-- | Load an entry from a given file.
|
||||
|
||||
loadEntry
|
||||
:: CompressionMethod -- ^ Compression method to use
|
||||
@@ -408,8 +409,8 @@ loadEntry t f path = do
|
||||
addPending (I.SinkEntry t src s)
|
||||
addPending (I.SetModTime modTime s)
|
||||
|
||||
-- | Copy entry “as is” from another .ZIP archive. If the entry does not
|
||||
-- exists in that archive, 'EntryDoesNotExist' will be eventually thrown.
|
||||
-- | Copy an entry “as is” from another zip archive. If the entry does not
|
||||
-- exist in that archive, 'EntryDoesNotExist' will be eventually thrown.
|
||||
|
||||
copyEntry
|
||||
:: Path b File -- ^ Path to archive to copy from
|
||||
@@ -420,8 +421,8 @@ copyEntry path s' s = do
|
||||
apath <- liftIO (canonicalizePath path)
|
||||
addPending (I.CopyEntry apath s' s)
|
||||
|
||||
-- | Add entire directory to archive. Please note that due to design of the
|
||||
-- library, empty sub-directories won't be added.
|
||||
-- | 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 the same exceptions as 'listDirRecur' and
|
||||
-- 'InvalidEntrySelector'.
|
||||
@@ -435,8 +436,8 @@ packDirRecur t f path = do
|
||||
files <- snd <$> liftIO (listDirRecur path)
|
||||
mapM_ (loadEntry t f) files
|
||||
|
||||
-- | Rename entry in archive. If the entry does not exist, nothing will
|
||||
-- happen.
|
||||
-- | Rename an entry in the archive. If the entry does not exist, nothing
|
||||
-- will happen.
|
||||
|
||||
renameEntry
|
||||
:: EntrySelector -- ^ Original entry name
|
||||
@@ -444,7 +445,8 @@ renameEntry
|
||||
-> ZipArchive ()
|
||||
renameEntry old new = addPending (I.RenameEntry old new)
|
||||
|
||||
-- | Delete entry from archive, if it does not exist, nothing will happen.
|
||||
-- | Delete an entry from the archive, if it does not exist, nothing will
|
||||
-- happen.
|
||||
|
||||
deleteEntry :: EntrySelector -> ZipArchive ()
|
||||
deleteEntry s = addPending (I.DeleteEntry s)
|
||||
@@ -458,24 +460,24 @@ recompress
|
||||
-> ZipArchive ()
|
||||
recompress t s = addPending (I.Recompress t s)
|
||||
|
||||
-- | Set entry comment, if that entry does not exist, nothing will
|
||||
-- happen. Note that if binary representation of comment is longer than
|
||||
-- | 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.
|
||||
|
||||
setEntryComment
|
||||
:: Text -- ^ Text of the comment
|
||||
-> EntrySelector -- ^ Name of entry to comment upon
|
||||
-> EntrySelector -- ^ Name of entry to comment on
|
||||
-> ZipArchive ()
|
||||
setEntryComment text s = addPending (I.SetEntryComment text s)
|
||||
|
||||
-- | Delete entry's comment, if that entry does not exist, nothing will
|
||||
-- | Delete an entry's comment, if that entry does not exist, nothing will
|
||||
-- happen.
|
||||
|
||||
deleteEntryComment :: EntrySelector -> ZipArchive ()
|
||||
deleteEntryComment s = addPending (I.DeleteEntryComment s)
|
||||
|
||||
-- | Set “last modification” date\/time. Specified entry may be missing, in
|
||||
-- that case this action has no effect.
|
||||
-- | Set the “last modification” date\/time. The specified entry may be
|
||||
-- missing, in that case the action has no effect.
|
||||
|
||||
setModTime
|
||||
:: UTCTime -- ^ New modification time
|
||||
@@ -483,8 +485,8 @@ setModTime
|
||||
-> ZipArchive ()
|
||||
setModTime time s = addPending (I.SetModTime time s)
|
||||
|
||||
-- | Add an extra field. Specified entry may be missing, in that case this
|
||||
-- action has no effect.
|
||||
-- | Add an extra field. The specified entry may be missing, in that case
|
||||
-- this action has no effect.
|
||||
|
||||
addExtraField
|
||||
:: Word16 -- ^ Tag (header id) of extra field to add
|
||||
@@ -493,7 +495,7 @@ addExtraField
|
||||
-> ZipArchive ()
|
||||
addExtraField n b s = addPending (I.AddExtraField n b s)
|
||||
|
||||
-- | Delete an extra field by its type (tag). Specified entry may be
|
||||
-- | Delete an extra field by its type (tag). The specified entry may be
|
||||
-- missing, in that case this action has no effect.
|
||||
|
||||
deleteExtraField
|
||||
@@ -502,30 +504,30 @@ deleteExtraField
|
||||
-> ZipArchive ()
|
||||
deleteExtraField n s = addPending (I.DeleteExtraField n s)
|
||||
|
||||
-- | Perform an action on every entry in archive.
|
||||
-- | Perform an action on every entry in the archive.
|
||||
|
||||
forEntries
|
||||
:: (EntrySelector -> ZipArchive ()) -- ^ Action to perform
|
||||
-> ZipArchive ()
|
||||
forEntries action = getEntries >>= mapM_ action . M.keysSet
|
||||
|
||||
-- | Set comment of entire archive.
|
||||
-- | Set comment of the entire archive.
|
||||
|
||||
setArchiveComment :: Text -> ZipArchive ()
|
||||
setArchiveComment text = addPending (I.SetArchiveComment text)
|
||||
|
||||
-- | Delete archive comment if it's present.
|
||||
-- | Delete the archive comment if it's present.
|
||||
|
||||
deleteArchiveComment :: ZipArchive ()
|
||||
deleteArchiveComment = addPending I.DeleteArchiveComment
|
||||
|
||||
-- | Undo changes to specific archive entry.
|
||||
-- | Undo changes to a specific archive entry.
|
||||
|
||||
undoEntryChanges :: EntrySelector -> ZipArchive ()
|
||||
undoEntryChanges s = modifyActions f
|
||||
where f = S.filter ((/= Just s) . I.targetEntry)
|
||||
|
||||
-- | Undo changes to archive as a whole (archive's comment).
|
||||
-- | Undo changes to the archive as a whole (archive's comment).
|
||||
|
||||
undoArchiveChanges :: ZipArchive ()
|
||||
undoArchiveChanges = modifyActions f
|
||||
@@ -537,12 +539,12 @@ undoAll :: ZipArchive ()
|
||||
undoAll = modifyActions (const S.empty)
|
||||
|
||||
-- | Archive contents are not modified instantly, but instead changes are
|
||||
-- collected as “pending actions” that should be committed in order to
|
||||
-- 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.
|
||||
-- automatically when 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.
|
||||
|
||||
commit :: ZipArchive ()
|
||||
commit = do
|
||||
@@ -566,23 +568,24 @@ commit = do
|
||||
----------------------------------------------------------------------------
|
||||
-- Helpers
|
||||
|
||||
-- | Get path of actual archive file from inside of 'ZipArchive' monad.
|
||||
-- | Get the path of the actual archive file from inside of 'ZipArchive'
|
||||
-- monad.
|
||||
|
||||
getFilePath :: ZipArchive (Path Abs File)
|
||||
getFilePath = ZipArchive (gets zsFilePath)
|
||||
|
||||
-- | Get collection of pending actions.
|
||||
-- | Get the collection of pending actions.
|
||||
|
||||
getPending :: ZipArchive (Seq I.PendingAction)
|
||||
getPending = ZipArchive (gets zsActions)
|
||||
|
||||
-- | Modify collection of pending actions in some way.
|
||||
-- | Modify the collection of pending actions in some way.
|
||||
|
||||
modifyActions :: (Seq I.PendingAction -> Seq I.PendingAction) -> ZipArchive ()
|
||||
modifyActions f = ZipArchive (modify g)
|
||||
where g st = st { zsActions = f (zsActions st) }
|
||||
|
||||
-- | Add new action to the list of pending actions.
|
||||
-- | Add a new action to the list of pending actions.
|
||||
|
||||
addPending :: I.PendingAction -> ZipArchive ()
|
||||
addPending a = modifyActions (|> a)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-- Stability : experimental
|
||||
-- Portability : portable
|
||||
--
|
||||
-- Support for decoding of CP437 text.
|
||||
-- Support for decoding of CP 437 text.
|
||||
|
||||
module Codec.Archive.Zip.CP437
|
||||
( decodeCP437 )
|
||||
@@ -29,7 +29,7 @@ decodeCP437 bs = T.unfoldrN
|
||||
(fmap (first decodeByteCP437) . B.uncons)
|
||||
bs
|
||||
|
||||
-- | Decode single byte of CP437 encoded text.
|
||||
-- | Decode a single byte of CP437 encoded text.
|
||||
|
||||
decodeByteCP437 :: Word8 -> Char
|
||||
decodeByteCP437 byte = chr $ case byte of
|
||||
|
||||
@@ -109,7 +109,7 @@ data EditingActions = EditingActions
|
||||
, eaExtraField :: Map EntrySelector (Map Word16 ByteString)
|
||||
, eaDeleteField :: Map EntrySelector (Map Word16 ()) }
|
||||
|
||||
-- | Origins of entries that can be streamed into archive.
|
||||
-- | Origin of entries that can be streamed into archive.
|
||||
|
||||
data EntryOrigin
|
||||
= GenericOrigin
|
||||
@@ -160,8 +160,8 @@ zipVersion = Version [4,6] []
|
||||
----------------------------------------------------------------------------
|
||||
-- Higher-level operations
|
||||
|
||||
-- | Scan central directory of an archive and return its description
|
||||
-- 'ArchiveDescription' as well as collection of its entries.
|
||||
-- | Scan the central directory of an archive and return its description
|
||||
-- 'ArchiveDescription' as well as a collection of its entries.
|
||||
--
|
||||
-- This operation may fail with:
|
||||
--
|
||||
@@ -177,13 +177,13 @@ zipVersion = Version [4,6] []
|
||||
-- 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.
|
||||
-- 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.
|
||||
|
||||
scanArchive
|
||||
:: Path Abs File -- ^ Path to archive to scan
|
||||
@@ -259,7 +259,7 @@ commit path ArchiveDescription {..} entries xs =
|
||||
writeCD h comment (copiedCD `M.union` sunkCD)
|
||||
|
||||
-- | Determine what comment in new archive will look like given its original
|
||||
-- value and collection of pending actions.
|
||||
-- value and a collection of pending actions.
|
||||
|
||||
predictComment :: Maybe Text -> Seq PendingAction -> Maybe Text
|
||||
predictComment original xs =
|
||||
@@ -269,19 +269,18 @@ predictComment original xs =
|
||||
Just (SetArchiveComment txt) -> Just txt
|
||||
Just _ -> Nothing
|
||||
|
||||
-- | Transform map representing existing entries into collection of actions
|
||||
-- that re-create those entires.
|
||||
-- | Transform a map representing existing entries into a collection of
|
||||
-- actions that re-create those entires.
|
||||
|
||||
toRecreatingActions
|
||||
:: Path Abs File -- ^ Name of archive file where entires are found
|
||||
:: Path Abs File -- ^ Name of the archive file where entires are found
|
||||
-> Map EntrySelector EntryDescription -- ^ Actual list of entires
|
||||
-> Seq PendingAction -- ^ Actions that recreate the archive entries
|
||||
toRecreatingActions path entries = E.foldl' f S.empty (M.keysSet entries)
|
||||
where f s e = s |> CopyEntry path e e
|
||||
|
||||
-- | Transform collection of 'PendingAction's into 'ProducingActions' and
|
||||
-- 'EditingActions' — collection of data describing how to create resulting
|
||||
-- archive.
|
||||
-- | Transform a collection of 'PendingAction's into 'ProducingActions' and
|
||||
-- 'EditingActions' — data that describes how to create resulting archive.
|
||||
|
||||
optimize
|
||||
:: Seq PendingAction -- ^ Collection of pending actions
|
||||
@@ -348,8 +347,8 @@ optimize = foldl' f
|
||||
if M.null n then Nothing else Just n
|
||||
er _ Nothing = Nothing
|
||||
|
||||
-- | Copy entries from another archive and write them into file associated
|
||||
-- with given handle. This actually can throw 'EntryDoesNotExist' if there
|
||||
-- | Copy entries from another archive and write them into the file
|
||||
-- associated with given handle. This can throw 'EntryDoesNotExist' if there
|
||||
-- is no such entry in that archive.
|
||||
|
||||
copyEntries
|
||||
@@ -369,7 +368,8 @@ copyEntries h path m e = do
|
||||
(sourceEntry path desc False) e
|
||||
return (M.fromList done)
|
||||
|
||||
-- | Sink entry from given stream into file associated with given 'Handle'.
|
||||
-- | Sink entry from given stream into the file associated with given
|
||||
-- 'Handle'.
|
||||
|
||||
sinkEntry
|
||||
:: Handle -- ^ Opened 'Handle' of zip archive file
|
||||
@@ -467,9 +467,9 @@ sinkData h compression = do
|
||||
, ddUncompressedSize = uncompressedSize }
|
||||
|
||||
-- | Append central directory entries and end of central directory record to
|
||||
-- file that given 'Handle' is associated with. Note that this automatically
|
||||
-- writes Zip64 end of central directory record and Zip64 end of central
|
||||
-- directory locator when necessary.
|
||||
-- the file that given 'Handle' is associated with. Note that this
|
||||
-- automatically writes Zip64 end of central directory record and Zip64 end
|
||||
-- of central directory locator when necessary.
|
||||
|
||||
writeCD
|
||||
:: Handle -- ^ Opened handle of zip archive file
|
||||
@@ -496,8 +496,8 @@ writeCD h comment m = do
|
||||
----------------------------------------------------------------------------
|
||||
-- Binary serialization
|
||||
|
||||
-- | Extract number of bytes between start of file name in local header and
|
||||
-- start of actual data.
|
||||
-- | Extract the number of bytes between start of file name in local header
|
||||
-- and start of actual data.
|
||||
|
||||
getLocalHeaderGap :: Get Integer
|
||||
getLocalHeaderGap = do
|
||||
@@ -519,8 +519,9 @@ getLocalHeaderGap = do
|
||||
getCD :: Get (Map EntrySelector EntryDescription)
|
||||
getCD = M.fromList . catMaybes <$> many getCDHeader
|
||||
|
||||
-- | Parse single central directory file header. If it's a directory or file
|
||||
-- compressed with unsupported compression method, 'Nothing' is returned.
|
||||
-- | Parse a single central directory file header. If it's a directory or
|
||||
-- file compressed with unsupported compression method, 'Nothing' is
|
||||
-- returned.
|
||||
|
||||
getCDHeader :: Get (Maybe (EntrySelector, EntryDescription))
|
||||
getCDHeader = do
|
||||
@@ -586,8 +587,8 @@ getExtraField = do
|
||||
body <- getBytes (fromIntegral size) -- content
|
||||
return (header, body)
|
||||
|
||||
-- | Get signature. If extracted data is not equal to provided signature,
|
||||
-- fail.
|
||||
-- | Get signature. If the extracted data is not equal to provided
|
||||
-- signature, fail.
|
||||
|
||||
getSignature :: Word32 -> Get ()
|
||||
getSignature sig = do
|
||||
@@ -861,7 +862,7 @@ infixl 1 >>+
|
||||
(>>+) :: IO (Maybe a) -> (a -> IO (Maybe b)) -> IO (Maybe b)
|
||||
a >>+ b = a >>= maybe (return Nothing) b
|
||||
|
||||
-- | Rename entry (key) in a 'Map'.
|
||||
-- | Rename an entry (key) in a 'Map'.
|
||||
|
||||
renameKey :: Ord k => k -> k -> Map k a -> Map k a
|
||||
renameKey ok nk m = case M.lookup ok m of
|
||||
@@ -944,13 +945,13 @@ fromCompressionMethod Store = 0
|
||||
fromCompressionMethod Deflate = 8
|
||||
fromCompressionMethod BZip2 = 12
|
||||
|
||||
-- | Check if entry with these parameters needs Zip64 extension.
|
||||
-- | Check if an entry with these parameters needs Zip64 extension.
|
||||
|
||||
needsZip64 :: EntryDescription -> Bool
|
||||
needsZip64 EntryDescription {..} = any (>= ffffffff)
|
||||
[edOffset, edCompressedSize, edUncompressedSize]
|
||||
|
||||
-- | Determine “version needed to extract” that should be written headers
|
||||
-- | Determine “version needed to extract” that should be written to headers
|
||||
-- given need of Zip64 feature and compression method.
|
||||
|
||||
getZipVersion :: Bool -> Maybe CompressionMethod -> Version
|
||||
@@ -962,7 +963,7 @@ getZipVersion zip64 m = max zip64ver mver
|
||||
Just Deflate -> [2,0]
|
||||
Just BZip2 -> [4,6]
|
||||
|
||||
-- | Return decompressing 'Conduit' corresponding to given compression
|
||||
-- | Return decompressing 'Conduit' corresponding to the given compression
|
||||
-- method.
|
||||
|
||||
decompressingPipe
|
||||
|
||||
+16
-16
@@ -59,21 +59,21 @@ import qualified System.FilePath.Windows as Windows
|
||||
----------------------------------------------------------------------------
|
||||
-- Entry selector
|
||||
|
||||
-- | This data type serves for naming and selection of archive
|
||||
-- entries. It can be created only with help of smart constructor
|
||||
-- | This data type serves for naming and selection of archive entries. It
|
||||
-- can be created only with the help of the 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.
|
||||
-- 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.
|
||||
|
||||
newtype EntrySelector = EntrySelector
|
||||
{ unES :: NonEmpty (CI String)
|
||||
@@ -83,7 +83,7 @@ newtype EntrySelector = EntrySelector
|
||||
instance Show EntrySelector where
|
||||
show = show . unEntrySelector
|
||||
|
||||
-- | Create 'EntrySelector' from @Path Rel File@. To avoid problems with
|
||||
-- | Create an '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:
|
||||
@@ -122,7 +122,7 @@ unEntrySelector = unES
|
||||
>>> parseRelFile
|
||||
>>> fromJust
|
||||
|
||||
-- | Get entry name given 'EntrySelector' in from that is suitable for
|
||||
-- | Get entry name given 'EntrySelector' in the from that is suitable for
|
||||
-- writing to file header.
|
||||
|
||||
getEntryName :: EntrySelector -> Text
|
||||
@@ -133,7 +133,7 @@ getEntryName = unES
|
||||
>>> concat
|
||||
>>> T.pack
|
||||
|
||||
-- | Exception describing various troubles you can have with
|
||||
-- | The exception describing various troubles you can have with
|
||||
-- 'EntrySelector'.
|
||||
|
||||
data EntrySelectorException
|
||||
@@ -150,7 +150,7 @@ instance Exception EntrySelectorException
|
||||
-- Entry description
|
||||
|
||||
-- | 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
|
||||
-- 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.
|
||||
|
||||
@@ -189,7 +189,7 @@ data ArchiveDescription = ArchiveDescription
|
||||
----------------------------------------------------------------------------
|
||||
-- Exceptions
|
||||
|
||||
-- | Bad things that can happen when you use the library.
|
||||
-- | The bad things that can happen when you use the library.
|
||||
|
||||
data ZipException
|
||||
= EntryDoesNotExist (Path Abs File) EntrySelector
|
||||
|
||||
@@ -32,11 +32,11 @@ terms of feature-set with libraries like `libzip` in C.
|
||||
## Why this library is written
|
||||
|
||||
There are a few libraries to work with Zip archives, yet every one of them
|
||||
provides only subset of all functionality user may need (obviously the
|
||||
libraries provide functionality that their authors needed) and otherwise is
|
||||
flawed in some way so it cannot be easily used in some situations. Let's
|
||||
examine all libraries available on Hackage to understand motivation for this
|
||||
package.
|
||||
provides only a subset of all the functionality a user may need (obviously
|
||||
the libraries provide functionality that their authors needed) and otherwise
|
||||
is flawed in some way so it cannot be easily used in some situations. Let's
|
||||
examine all the libraries available on Hackage to understand motivation for
|
||||
this package.
|
||||
|
||||
### zip-archive
|
||||
|
||||
@@ -45,20 +45,20 @@ simple to use. However it creates Zip archives purely, as `ByteStrings`s in
|
||||
memory that you can then write to the file system. This is not acceptable if
|
||||
you work with more-or-less big data. For example, if you have collection of
|
||||
files with total size of 500 MB and you want to pack them into an archive,
|
||||
you can easily consume up to 1 GB of memory (files plus resulting
|
||||
you can easily consume up to 1 GB of memory (the files plus resulting
|
||||
archive). Not always you can afford to do this or do this at scale. Even if
|
||||
you want just to look at list of archive entries it will read it into memory
|
||||
in all its entirety. For my use-case it's not acceptable.
|
||||
|
||||
### LibZip
|
||||
|
||||
This is bindings to C library
|
||||
[`libzip`](https://en.wikipedia.org/wiki/Libzip). There is always certain
|
||||
kind of trouble when you are using bindings. For example, you need to take
|
||||
care that target library is installed and its version is compatible with
|
||||
version of your binding. Yes, this means additional headaches. It should be
|
||||
just “plug and play” (if you're using Stack), but now you need to watch out
|
||||
for compatibility.
|
||||
This is a binding to C
|
||||
library [`libzip`](https://en.wikipedia.org/wiki/Libzip). There is always
|
||||
certain kind of trouble when you are using bindings. For example, you need
|
||||
to take care that target library is installed and its version is compatible
|
||||
with the version of your binding. Yes, this means additional headaches. It
|
||||
should be just “plug and play”, but now you need to watch out for
|
||||
compatibility.
|
||||
|
||||
It's not that bad with libraries that do not break their API for years, but
|
||||
it's not the case with `libzip`. As maintainer of `LibZip` puts it:
|
||||
@@ -69,9 +69,9 @@ it's not the case with `libzip`. As maintainer of `LibZip` puts it:
|
||||
|
||||
Now, on my machine I have version 1.0. To put the package on Stackage we had
|
||||
to use version 0.10, because Stackage uses Ubuntu to build packages and
|
||||
libraries on Ubuntu are always ancient. This means that I cannot use version
|
||||
of the library from Stackage, and I don't yet know what will be on the
|
||||
server.
|
||||
libraries on Ubuntu are always ancient. This means that I cannot use the
|
||||
version of the library from Stackage, and I don't yet know what will be on
|
||||
the server.
|
||||
|
||||
After much frustration with all these things I decided to avoid using of
|
||||
`LibZip`, because after all, this is not that sort of project that shouldn't
|
||||
@@ -80,16 +80,16 @@ safer to use.
|
||||
|
||||
### zip-conduit
|
||||
|
||||
This one uses the right approach: leverage good streaming library
|
||||
This one uses the right approach: leverage a good streaming library
|
||||
(`conduit`) for memory-efficient processing. This is however is not
|
||||
feature-rich and has certain problems (including programming style, it uses
|
||||
`error` if an entry is missing in archive, among other things), some of them
|
||||
are reported on its issue tracker. It also does not appear to be maintained
|
||||
(last sign of activity was on December 23, 2014).
|
||||
(the last sign of activity was on December 23, 2014).
|
||||
|
||||
## Features
|
||||
|
||||
The library supports all features specified in modern .ZIP specification
|
||||
The library supports all features specified in the modern .ZIP specification
|
||||
except for encryption and multi-disk archives. See more about this below.
|
||||
|
||||
For reference, here is a [copy of the specification](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT).
|
||||
@@ -104,8 +104,8 @@ For reference, here is a [copy of the specification](https://pkware.cachefly.net
|
||||
|
||||
The best way to add new compression method to the library is to write
|
||||
conduit that will do the compression and publish it as a library. `zip` can
|
||||
then depend on it and add it to the list of supported compression
|
||||
methods. Current list of compression methods reflects what is available on
|
||||
then depend on it and add it to the list of supported compression methods.
|
||||
The current list of compression methods reflects what is available on
|
||||
Hackage at the moment.
|
||||
|
||||
### Encryption
|
||||
@@ -129,8 +129,8 @@ how to get extracted data. The following methods are supported:
|
||||
|
||||
* *ByteString.* Use it only with small data.
|
||||
|
||||
* *Copy file from another archive.* Efficient operation, file is copied “as
|
||||
is” — no re-compression is performed.
|
||||
* *Copy file from another archive.* An efficient operation, file is copied
|
||||
“as is” — no re-compression is performed.
|
||||
|
||||
### ZIP64
|
||||
|
||||
@@ -166,17 +166,17 @@ archive.
|
||||
|
||||
## Quick start
|
||||
|
||||
The module `Codec.Archive.Zip` provides everything you need to manipulate
|
||||
Zip archives. There are three things that should be clarified right away, to
|
||||
avoid confusion in the future.
|
||||
The module `Codec.Archive.Zip` 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 `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.
|
||||
doing in your projects anyway. Even if you don't want to use the `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
|
||||
@@ -185,10 +185,10 @@ 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
|
||||
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 update, `commit` function is your
|
||||
however, you ever need to force update, the `commit` function is your
|
||||
friend. There are even “undo” functions, by the way.
|
||||
|
||||
Let's take a look at some examples that show how to accomplish most typical
|
||||
@@ -219,7 +219,7 @@ possible to extract contents of archive as strict `ByteString`:
|
||||
λ> withArchive archivePath (saveEntry entrySelector pathToFile)
|
||||
```
|
||||
|
||||
…and finally just unpack entire archive into some directory:
|
||||
…and finally just unpack the entire archive into some directory:
|
||||
|
||||
```haskell
|
||||
λ> withArchive archivePath (unpackInto destDir)
|
||||
@@ -227,9 +227,9 @@ possible to extract contents of archive as strict `ByteString`:
|
||||
|
||||
See also `getArchiveComment` and `getArchiveDescription`.
|
||||
|
||||
Modifying is also easy, efficient, and powerful. When you want to create new
|
||||
archive use `createArchive`, otherwise `withArchive` will do. To add entry
|
||||
from `ByteString`:
|
||||
Modifying is also easy, efficient, and powerful. When you want to create a
|
||||
new archive use `createArchive`, otherwise `withArchive` will do. To add
|
||||
entry from `ByteString`:
|
||||
|
||||
```haskell
|
||||
λ> createArchive archivePath (addEntry Store "Hello, World!" entrySelector)
|
||||
@@ -248,7 +248,7 @@ To add contents from some file, use `loadEntry`:
|
||||
λ> createArchive archivePath (loadEntry BZip2 toSelector myFilePath)
|
||||
```
|
||||
|
||||
Finally, you can copy entry from another archive without re-compression
|
||||
Finally, you can copy an entry from another archive without re-compression
|
||||
(unless you use `recompress`, see below):
|
||||
|
||||
```haskell
|
||||
|
||||
Reference in New Issue
Block a user