packages feed

keid-core-0.1.2.0: src/Resource/Compressed/Zstd.hs

module Resource.Compressed.Zstd where

import RIO

import Data.Typeable (typeOf)
import RIO.ByteString qualified as ByteString
import RIO.FilePath (takeExtension)

import Codec.Compression.Zstd qualified as Zstd

-- * Compressed container

newtype Compressed a = Compressed { getCompressed :: a }

compressBytes :: ByteString -> Compressed ByteString
compressBytes = Compressed . Zstd.compress Zstd.maxCLevel

decompressBytes :: Compressed ByteString -> Either CompressedError ByteString
decompressBytes (Compressed bytes) =
  case Zstd.decompress bytes of
    Zstd.Decompress buf ->
      Right buf
    Zstd.Skip ->
      Right mempty
    Zstd.Error str ->
      Left $ ZstdError (fromString str)

instance Typeable a => Show (Compressed a) where
  show (Compressed x) = "Compressed " <> show (typeOf x)

data CompressedError
  = ZstdError Text
  | EmptyFile FilePath
  deriving (Eq, Show)

instance Exception CompressedError

-- * Loading files

fromFileWith :: MonadIO m => (ByteString -> m b) -> (FilePath -> m b) -> FilePath -> m b
fromFileWith withBS withFilePath filePath
  | elem (takeExtension filePath) compressedExts =
      loadCompressed withBS filePath
  | otherwise =
      withFilePath filePath

loadCompressed :: MonadIO m => (ByteString -> m b) -> FilePath -> m b
loadCompressed withBS filePath = do
  bytes <- ByteString.readFile filePath
  case decompressBytes (Compressed bytes) of
    Right buf ->
      if ByteString.null buf then
        throwIO $ EmptyFile filePath
      else
        withBS buf
    Left err ->
      throwIO err

compressedExts :: [FilePath]
compressedExts =
  [ ".zst"
  , ".zstd"
  ]