data-embed-0.1.0.0: Data/Embed/Header.hs
{-# LANGUAGE BangPatterns #-}
-- | The bundle data type, representing a binary bundle attached to an
-- executable.
module Data.Embed.Header where
import Control.Monad
import qualified Data.ByteString as BS
import Data.ByteString.UTF8
import Data.Hashable
import qualified Data.IntMap.Strict as M
import Data.Serialize
import Data.Word
type FileMap = M.IntMap [(FilePath, (Word64, Word32))]
-- | "Magic number" identifying a structure as a bundle header: "BNDLLDNB"
bundleMagicNumber :: Word64
bundleMagicNumber = 0x424e444c4c444e42 -- "BNDLLDNB"
-- | Current version of the bundle format.
bundleCurrentVersion :: Word8
bundleCurrentVersion = 0
-- | Maximum file size of a file in the bundle.
bundleMaxFileSize :: Integer
bundleMaxFileSize = fromIntegral (maxBound :: Word32)
-- | Size of the bundle's static header: two Word64 + two Word32 + one Word8.
bundleHeaderStaticSize :: Integer
bundleHeaderStaticSize = 25
-- | The static header of a file bundle.
data StaticHeader = StaticHeader {
-- | The offset from the end of the file to the beginning of the file data
-- section.
hdrDataOffset :: !Word64,
-- | Total number of files in the bundle.
hdrNumFiles :: !Word32,
-- | Size of the dynamic part of the header, where file names, offsets and
-- sizes are stored.
hdrDynSize :: !Word32,
-- | Version of the bundle structure. Must always be less than or equal to
-- 'bundleCurrentVersion'
hdrVersion :: !Word8,
-- | Magic number. Must always be 'bundleMagicNumber'.
hdrMagicNumber :: !Word64
}
-- | A file bundle header. Contains basic metadata about the bundle itself, and
-- the metadata for all files contained therein.
data BundleHeader = BundleHeader {
-- | A hash map from file paths to the respective offsets and sizes.
hdrFiles :: !FileMap,
-- | Static part of the header.
hdrStatic :: !StaticHeader
}
-- | Build a static header with the specified number of files, size of the
-- dynamic header, and offset to the start of the data section.
mkStaticHdr :: Int -> Int -> Word64 -> StaticHeader
mkStaticHdr nfiles dynsize dataoff = StaticHeader {
hdrVersion = bundleCurrentVersion,
hdrMagicNumber = bundleMagicNumber,
hdrNumFiles = fromIntegral nfiles,
hdrDynSize = fromIntegral dynsize,
hdrDataOffset = dataoff
}
instance Serialize StaticHeader where
put hdr = do
putWord64le (hdrDataOffset hdr)
putWord32le (hdrNumFiles hdr)
putWord32le (hdrDynSize hdr)
putWord8 (hdrVersion hdr)
putWord64le (hdrMagicNumber hdr)
get =
StaticHeader <$> getWord64le
<*> getWord32le
<*> getWord32le
<*> getWord8
<*> getWord64le
getHdrFile :: Get (FilePath, (Word64, Word32))
getHdrFile = do
pathlen <- getWord32le
path <- getBytes (fromIntegral pathlen)
off <- getWord64le
sz <- getWord32le
return (toString path, (off, sz))
getHdrFiles :: Word32 -> Get FileMap
getHdrFiles = getFiles M.empty
where
getFiles !m 0 = pure m
getFiles !m n = do
f <- getHdrFile
getFiles (M.alter (ins f) (hash $ fst f) m) (n-1)
ins f (Just fs) = Just (f:fs)
ins f _ = Just [f]
putHdrFiles :: FileMap -> BS.ByteString
putHdrFiles m = runPut $ forM_ (concat $ M.elems m) $ \(p, (off, sz)) -> do
let p' = fromString p
putWord32le (fromIntegral $ BS.length p')
putByteString p'
putWord64le off
putWord32le sz