{-# LANGUAGE FlexibleContexts #-}

-- |
-- Module: Codec.RPM.Conduit
-- Copyright: (c) 2016-2017 Red Hat, Inc.
-- License: LGPL
--
-- Maintainer: https://github.com/weldr
-- Stability: stable
-- Portability: portable
--
-- A module for interacting with an 'RPM' record using conduits.

module Codec.RPM.Conduit(parseRPMC,
                         payloadC,
                         payloadContentsC)
 where

import           Control.Monad.Catch(MonadThrow)
import           Control.Monad.Except(MonadError, throwError)
import           Control.Monad.Trans.Resource(MonadResource)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import           Data.Conduit((.|), Conduit, awaitForever, yield)
import           Data.Conduit.Attoparsec(ParseError, conduitParserEither)
import           Data.Conduit.Lzma(decompress)
import           Data.CPIO(Entry, readCPIO)

import Codec.RPM.Parse(parseRPM)
import Codec.RPM.Types(RPM(..))

-- | Like 'parseRPM', but puts the result into a 'Conduit' as an 'Either', containing either a
-- 'ParseError' or an 'RPM'.  The result can be extracted with 'Control.Monad.Except.runExceptT',
-- like so:
--
-- > import Conduit((.|), runConduitRes, sourceFile)
-- > import Control.Monad.Except(runExceptT)
-- > result <- runExceptT $ runConduitRes $ sourceFile "some.rpm" .| parseRPMC .| someConsumer
--
-- On success, the 'RPM' record will be passed down the conduit for futher processing or
-- consumption.  On error, the rest of the conduit will be skipped and the 'ParseError' will
-- be returned as the result to be dealt with.
parseRPMC :: MonadError ParseError m => Conduit C.ByteString m RPM
parseRPMC =
    conduitParserEither parseRPM .| consumer
 where
    consumer = awaitForever $ either throwError (yield . snd)

-- | Extract the package payload from an 'RPM', returning it in the conduit.
payloadC :: Monad m => Conduit RPM m BS.ByteString
payloadC = awaitForever (yield . rpmArchive)

-- | Extract the package payload from an 'RPM', decompress it, and return each element of
-- the payload as a 'Data.CPIO.Entry'.
payloadContentsC :: (MonadResource m, MonadThrow m) => Conduit RPM m Entry
payloadContentsC = payloadC
                .| decompress Nothing
                .| readCPIO