packages feed

datastar-hs-1.0.2.0: src/Hypermedia/Datastar/Compression/Brotli.hs

module Hypermedia.Datastar.Compression.Brotli
  ( brotli
  , brotliWith
  , defaultBrotliParams
  )
where

import Codec.Compression.Brotli qualified as B
import Data.ByteString qualified as BS
import Data.ByteString.Builder qualified as BSB
import Data.ByteString.Lazy qualified as BL
import Control.Concurrent.MVar

import Hypermedia.Datastar.WAI (Compressor (..))

brotli :: Compressor
brotli = brotliWith defaultBrotliParams

brotliWith :: B.CompressParams -> Compressor
brotliWith params = Compressor "br" wrap
  where
    wrap rawWrite rawFlush = do
      csM <- B.compressIO params >>= newMVar

      let write builder = do
            let bs = BL.toStrict $ BSB.toLazyByteString builder
            if BS.null bs
              then pure ()
              else modifyMVar_ csM $ \case 
                     B.CompressInputRequired _ supply ->
                       supply bs >>= doRawWrites rawWrite
                     other -> pure other

          flush = do
            modifyMVar_ csM $ \case
              B.CompressInputRequired doFlush _ ->
                doFlush >>= doRawWrites rawWrite
              other -> pure other
            rawFlush

          finish = do
            modifyMVar_ csM $ \case
              B.CompressInputRequired _ supply ->
                supply BS.empty >>= doRawWrites rawWrite
              other -> pure other
            rawFlush

      return (write, flush, finish)

doRawWrites
  :: (BSB.Builder -> IO ())
  -> B.CompressStream IO
  -> IO (B.CompressStream IO)
doRawWrites rawWrite cs = case cs of
  B.CompressOutputAvailable chunk nextAction -> do
    rawWrite $ BSB.byteString chunk
    cs' <- nextAction
    doRawWrites rawWrite cs'
  _ -> pure cs

defaultBrotliParams :: B.CompressParams
defaultBrotliParams =
  B.defaultCompressParams
    { B.compressMode = B.CompressionModeText
    , B.compressLevel = B.CompressionLevel5
    , B.compressWindowSize = B.CompressionWindowBits24
    }