packages feed

zlib-yaftee-0.1.0.0: src/Control/Monad/Yaftee/Pipe/Zlib.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE BlockArguments, LambdaCase, OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables, RankNTypes, TypeApplications #-}
{-# LANGUAGE RequiredTypeArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

module Control.Monad.Yaftee.Pipe.Zlib (

	-- * RUN

	run, ByteString,

	-- * DEFLATE

	deflate, DeflateOptions(..),

	-- * INFLATE

	inflate,

	-- * C BYTE ARRAY

	CByteArray, cByteArrayMalloc, cByteArrayFree

	) where

import Foreign.Ptr
import Foreign.C.String
import Foreign.C.ByteArray qualified as CByteArray
import Control.Monad
import Control.Monad.ToolsYj
import Control.Monad.Primitive
import Control.Monad.Yaftee.Eff qualified as Eff
import Control.Monad.Yaftee.Pipe qualified as Pipe
import Control.Monad.Yaftee.State qualified as State
import Control.Monad.Yaftee.Except qualified as Except
import Control.HigherOpenUnion qualified as U
import Data.HigherFunctor qualified as HFunctor

import Codec.Compression.Zlib.Structure.Core qualified as Zlib
import Codec.Compression.Zlib.Constant.Core qualified as Zlib
import Codec.Compression.Zlib.Basic.Core qualified as Zlib
import Codec.Compression.Zlib.Advanced.Core qualified as Zlib

import Data.ByteString qualified as BS
import Data.ByteString.FingerTree qualified as BSF
import Data.ByteString.FingerTree.CString qualified as BSF

-- import Debug.Trace qualified as Trace

trace :: String -> a -> a
trace = const id -- Trace.trace

run :: forall nm es i o r . HFunctor.Loose (U.U es) =>
	Eff.E (State.Named nm (Maybe ByteString) ': es) i o r ->
	Eff.E es i o r
run = (fst <$>) . (`State.runN` (Nothing :: Maybe ByteString))

newtype ByteString = ByteString { unByteString :: BSF.ByteString } deriving Show

newtype CByteArray s = CByteArray { unCByteArray :: CByteArray.B } deriving Show

cByteArrayMalloc :: PrimMonad m => Int -> m (CByteArray (PrimState m))
cByteArrayMalloc n = unsafeIOToPrim $ CByteArray <$> CByteArray.malloc n

cByteArrayFree :: PrimMonad m => CByteArray (PrimState m) -> m ()
cByteArrayFree (CByteArray ba) = unsafeIOToPrim $ CByteArray.free ba

data DeflateOptions = DeflateOptions {
	deflateOptionsCompressionLevel :: Zlib.CompressionLevel,
	deflateOptionsCompressionMethod :: Zlib.CompressionMethod,
	deflateOptionsWindowBits :: Zlib.WindowBits,
	deflateOptionsMemLevel :: Zlib.MemLevel,
	deflateOptionsCompressionStrategy :: Zlib.CompressionStrategy }
	deriving Show

deflate :: forall nm m -> (
	PrimBase m,
	U.Member Pipe.P es, U.Member (State.Named nm (Maybe ByteString)) es,
	U.Member (Except.E Zlib.ReturnCode) es, U.Base (U.FromFirst m) es ) =>
	DeflateOptions ->
	CByteArray (PrimState m) -> CByteArray (PrimState m) ->
	Eff.E es BSF.ByteString BSF.ByteString BSF.ByteString
deflate nm m dos bai bao = do
	strm <- initialize nm m
		(\s os -> Zlib.deflateInit2 s
			(deflateOptionsCompressionLevel os)
			(deflateOptionsCompressionMethod os)
			(deflateOptionsWindowBits os)
			(deflateOptionsMemLevel os)
			(deflateOptionsCompressionStrategy os))
		dos bai bao
	doWhile_ $ body nm m Zlib.deflate bai bao strm
	finalize @m Zlib.deflateEnd strm
	restOfInput nm m strm

inflate :: forall nm m -> (
	PrimBase m,
	U.Member Pipe.P es, U.Member (State.Named nm (Maybe ByteString)) es,
	U.Member (Except.E Zlib.ReturnCode) es, U.Base (U.FromFirst m) es ) =>
	Zlib.WindowBits ->
	CByteArray (PrimState m) -> CByteArray (PrimState m) ->
	Eff.E es BSF.ByteString BSF.ByteString BSF.ByteString
inflate nm m wbs bai bao = do
	strm <- initialize nm m Zlib.inflateInit2 wbs bai bao
	doWhile_ $ body nm m Zlib.inflate bai bao strm
	finalize @m Zlib.inflateEnd strm
	restOfInput nm m strm

initialize :: forall nm m -> (
	PrimBase m,
	U.Member Pipe.P es, U.Member (State.Named nm (Maybe ByteString)) es,
	U.Member (Except.E Zlib.ReturnCode) es, U.Base (U.FromFirst m) es ) =>
	(forall m' . PrimBase m' =>
		Zlib.StreamPrim (PrimState m') -> arg -> m' Zlib.ReturnCode) ->
	arg -> CByteArray (PrimState m) -> CByteArray (PrimState m) ->
	Eff.E es BSF.ByteString BSF.ByteString (Zlib.StreamPrim (PrimState m))
initialize nm m f a
	(CByteArray (castPtr -> i, ni))
	(CByteArray (o, (fromIntegral -> no'))) = do
	bs0 <- awaitInput
	((castPtr -> i0, fromIntegral -> n0), ebs0) <-
		Eff.effBase . unsafeIOToPrim @m $ BSF.poke (i, ni) bs0
	either (const $ pure ()) (putInput nm ByteString) ebs0
	strm <- Eff.effBase $ Zlib.streamThaw @m Zlib.streamInitial {
		Zlib.streamNextIn = i0, Zlib.streamAvailIn = n0,
		Zlib.streamNextOut = o, Zlib.streamAvailOut = no' }
	rc0 <- Eff.effBase $ f @m strm a
	when (rc0 /= Zlib.Ok) $ Except.throw rc0
	pure strm

awaitInput :: U.Member Pipe.P es => Eff.E es BSF.ByteString o BSF.ByteString
awaitInput = do
	bs <- Pipe.await
	if BSF.null bs then awaitInput else pure bs

awaitInputMaybe :: U.Member Pipe.P es => (i -> Bool) -> Eff.E es i o (Maybe i)
awaitInputMaybe p = Pipe.awaitMaybe >>= \case
	Nothing -> pure Nothing
	Just bs -> if p bs then awaitInputMaybe p else pure $ Just bs

finalize :: forall m es i o . ( -- forall m -> (
	U.Member (Except.E Zlib.ReturnCode) es, U.Base (U.FromFirst m) es ) =>
	(Zlib.StreamPrim (PrimState m) -> m Zlib.ReturnCode) ->
	Zlib.StreamPrim (PrimState m) -> Eff.E es i o ()
finalize f strm = do
	rc <- Eff.effBase $ f strm
	trace "FINALIZE" $ pure ()
	when (rc /= Zlib.Ok) do
--		cmsg <- Eff.effBase $ Zlib.msg @m strm
--		msg <- Eff.effBase . unsafeIOToPrim @m $ peekCString cmsg
--		trace msg $ pure ()
		Except.throw rc

body :: forall nm m -> (
	PrimBase m,
	U.Member Pipe.P es,
	U.Member (State.Named nm (Maybe ByteString)) es,
	U.Member (Except.E Zlib.ReturnCode) es,
	U.Base (U.FromFirst m) es ) =>
	(forall m' . PrimBase m' => Zlib.StreamPrim (PrimState m') -> Zlib.Flush -> m' Zlib.ReturnCode) ->
	CByteArray (PrimState m) -> CByteArray (PrimState m) ->
	Zlib.StreamPrim (PrimState m) ->
	Eff.E es BSF.ByteString BSF.ByteString Bool
body nm m f
	(CByteArray (castPtr -> i, ni))
	(CByteArray (o@(castPtr -> o'), no@(fromIntegral -> no')))
	strm = do
		rc <- Eff.effBase $ f @m strm Zlib.NoFlush
--		rc <- Eff.effBase $ f @m strm Zlib.FullFlush
		ai <- Eff.effBase @m $ Zlib.availIn strm
		(fromIntegral -> ao) <- Eff.effBase @m $ Zlib.availOut strm
		when (rc `notElem` [Zlib.Ok, Zlib.StreamEnd]) do
			cmsg <- Eff.effBase $ Zlib.msg @m strm
			msg <- Eff.effBase . unsafeIOToPrim @m $ peekCString cmsg
			trace msg $ pure ()
			Except.throw rc
		Pipe.yield =<< Eff.effBase
			(unsafeIOToPrim @m $ BSF.peek (o', no - ao))
		Eff.effBase $ Zlib.setNextOut @m strm o no'
		whenDef (rc /= Zlib.StreamEnd) (rc /= Zlib.StreamEnd && ai == 0) do
			minp <- getInput nm BSF.null unByteString
			case minp of
				Nothing -> do
					doWhile_ do
						rc' <- Eff.effBase $ f @m strm Zlib.Finish
						(fromIntegral -> ao') <- Eff.effBase @m $ Zlib.availOut strm
						Pipe.yield =<< Eff.effBase
							(unsafeIOToPrim @m $ BSF.peek (o', no - ao'))
						trace (show rc') $ pure ()
						Eff.effBase $ Zlib.setNextOut @m strm o no'
						pure $ rc' /= Zlib.StreamEnd
					pure False
				Just inp -> do
					((castPtr -> i', fromIntegral -> n), ebs) <- Eff.effBase
						. unsafeIOToPrim @m
						$ BSF.poke (i, ni) inp
					either (const $ pure ()) (putInput nm ByteString) ebs
					Eff.effBase $ Zlib.setNextIn @m strm i' n
					pure $ rc /= Zlib.StreamEnd

restOfInput :: forall nm m -> (
	PrimBase m,
	U.Member (State.Named nm (Maybe ByteString)) es,
	U.Base (U.FromFirst m) es ) =>
	Zlib.StreamPrim (PrimState m) ->
	Eff.E es i o BSF.ByteString
restOfInput nm m strm = do
	(castPtr -> i') <- Eff.effBase @m $ Zlib.nextIn strm
	(fromIntegral -> ai) <- Eff.effBase @m $ Zlib.availIn strm
	rbs <- Eff.effBase @m . unsafeIOToPrim $ BS.packCStringLen (i', ai)
	ByteString bs' <- maybe (ByteString "") id <$> State.getN nm
	pure $ rbs BSF.:<| bs'

getInput :: forall es i' i o . forall nm ->
	(U.Member Pipe.P es, U.Member (State.Named nm (Maybe i')) es) =>
	(i -> Bool) -> (i' -> i) -> Eff.E es i o (Maybe i)
getInput nm p un = State.getN nm
	>>= maybe (awaitInputMaybe p) ((<$ State.putN @(Maybe i') nm Nothing) . Just . un)

putInput :: forall nm -> (U.Member (State.Named nm (Maybe i')) es) =>
	(i -> i') -> i -> Eff.E es i o ()
putInput nm to = State.putN nm . Just . to