packages feed

streamly-archive-0.0.1: src/Streamly/External/Archive.hs

--------------------------------------------------------------------------------

{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}

--------------------------------------------------------------------------------

module Streamly.External.Archive
    (
    -- ** Read
    readArchive,

    -- ** Header
    Header,
    FileType (..),
    headerFileType,
    headerPathName,
    headerPathNameUtf8,
    headerSize) where

--------------------------------------------------------------------------------

import Control.Exception (mask_)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.ByteString (ByteString)
import Data.Int (Int64)
import Data.Void (Void)
import Foreign (Ptr, free, malloc)
import Foreign.C.Types (CChar, CSize)
import Streamly.Internal.Data.Stream.StreamD.Type (Step (..))
import Streamly.Internal.Data.Unfold (supply)
import Streamly.Internal.Data.Unfold.Types (Unfold (..))

import qualified Data.ByteString as B
import qualified Streamly.Internal.Data.Stream.StreamD as D

import Streamly.External.Archive.Internal.Foreign (Entry, FileType (..),
    archive_entry_filetype, archive_entry_pathname, archive_entry_pathname_utf8, archive_entry_size,
    archive_read_data_block, archive_read_free, archive_read_new, archive_read_next_header,
    archive_read_open_filename, archive_read_support_filter_all, archive_read_support_format_all)

--------------------------------------------------------------------------------

-- | Header information for an entry in the archive.
newtype Header = Header Entry

{-# INLINE headerFileType #-}
headerFileType :: Header -> IO (Maybe FileType)
headerFileType (Header e) = archive_entry_filetype e

{-# INLINE headerPathName #-}
headerPathName :: Header -> IO (Maybe ByteString)
headerPathName (Header e) = archive_entry_pathname e

{-# INLINE headerPathNameUtf8 #-}
headerPathNameUtf8 :: Header -> IO (Maybe ByteString)
headerPathNameUtf8 (Header e) = archive_entry_pathname_utf8 e

{-# INLINE headerSize #-}
headerSize :: Header -> IO (Maybe Int)
headerSize (Header e) = archive_entry_size e

-- | Creates an unfold with which we can stream data out of the given archive.
{-# INLINE readArchive #-}
readArchive :: (MonadIO m) => FilePath -> Unfold m Void (Either Header ByteString)
readArchive fp = flip supply () $
    Unfold
        (\(arch, buf, sz, offs, pos, ref, readHeader) ->
            if readHeader then do
                me <- liftIO $ archive_read_next_header arch
                case me of
                    Nothing -> do
                        liftIO $ D.runIORefFinalizer ref
                        return Stop
                    Just e -> do
                        return $ Yield (Left $ Header e) (arch, buf, sz, offs, 0, ref, False)
            else do
                (bs, done) <- liftIO $ archive_read_data_block arch buf sz offs pos
                return $
                    if B.length bs > 0 then
                        Yield (Right bs) (arch, buf, sz, offs, pos + fromIntegral (B.length bs), ref, done)
                    else
                        Skip (arch, buf, sz, offs, pos, ref, done))
        (\() -> do
            (arch, buf, sz, offs, ref) <- liftIO . mask_ $ do
                arch <- liftIO archive_read_new
                buf :: Ptr (Ptr CChar) <- liftIO malloc
                sz :: Ptr CSize <- liftIO malloc
                offs :: Ptr Int64 <- liftIO malloc
                ref <- D.newFinalizedIORef $ archive_read_free arch >> free buf >> free sz >> free offs
                return (arch, buf, sz, offs, ref)
            liftIO $ archive_read_support_filter_all arch
            liftIO $ archive_read_support_format_all arch
            liftIO $ archive_read_open_filename arch fp
            return (arch, buf, sz, offs, 0, ref, True))

--------------------------------------------------------------------------------