sak-0.1.2.4: src/Compression.cpphs
module Compression ( Compression (Lzma, Lz4, Lzip, BZip)
, CompressionLevel (..)
, detectCompression
, toCompressor
, toDecompressor
, toFileCompressor
, toFileDecompressor
, uncompressedExt
, check
, ext
-- * Utilities
, fileSize
) where
#ifdef BROTLI
import qualified Codec.Compression.Brotli as Br
#endif
import qualified Codec.Compression.BZip as BZip
import qualified Codec.Compression.GZip as GZip
import qualified Codec.Compression.Lzma as Lzma
#ifdef SNAPPY
import qualified Codec.Compression.Snappy.BSL as Snappy
#endif
import qualified Codec.Compression.Zlib as Zlib
import qualified Codec.Compression.Zstd.Lazy as Zstd
import qualified Codec.Lz4 as Lz4
import qualified Codec.Lzip as Lzip
import qualified Data.ByteString.Lazy as BSL
import Data.List (isSuffixOf)
import System.FilePath (dropExtension, (-<.>))
import System.IO (IOMode (ReadMode), hFileSize, withFile)
data CompressionLevel = Best
| Fastest
| Default
| Custom !Int
data Compression = Lzma
| Lzip
| BZip
| GZip
| Zstd
#ifdef BROTLI
| Brotli
#endif
#ifdef SNAPPY
| Snappy
#endif
| Lz4
| Z
| None
deriving (Enum)
ext :: Compression -> String
ext Lzma = ".xz"
ext Lzip = ".lz"
ext BZip = ".bz2"
ext GZip = ".gz"
ext Zstd = ".zst"
ext Lz4 = ".lz4"
ext Z = ".Z"
#ifdef BROTLI
ext Brotli = ".br"
#endif
#ifdef SNAPPY
ext Snappy = ".sz"
#endif
ext None = ""
uncompressedExt :: FilePath -> FilePath
uncompressedExt fp | ".tlz" `isSuffixOf` fp = fp -<.> ".tar"
| ".txz" `isSuffixOf` fp = fp -<.> ".tar"
| ".tlz" `isSuffixOf` fp = fp -<.> ".tar"
| ".tgz" `isSuffixOf` fp = fp -<.> ".tar"
| ".cpgz" `isSuffixOf` fp = fp -<.> ".cpio"
| ".cpbz2" `isSuffixOf` fp = fp -<.> ".cpio"
| ".xcfgz" `isSuffixOf` fp = fp -<.> ".xcf"
| ".xcfbz2" `isSuffixOf` fp = fp -<.> ".xcf"
| ".tbz2" `isSuffixOf` fp = fp -<.> "tar"
| ".tbz" `isSuffixOf` fp = fp -<.> "tar"
| ".tbr" `isSuffixOf` fp = fp -<.> "tar"
| otherwise = dropExtension fp
detectCompression :: FilePath -> Compression
detectCompression fp
| ".xz" `isSuffixOf` fp = Lzma
| ".txz" `isSuffixOf` fp = Lzma
| ".lz" `isSuffixOf` fp = Lzip
| ".tlz" `isSuffixOf` fp = Lzip
| ".tbz2" `isSuffixOf` fp = BZip
| ".tbz" `isSuffixOf` fp = BZip
| ".bz2" `isSuffixOf` fp = BZip
| ".gz" `isSuffixOf` fp = GZip
| ".tgz" `isSuffixOf` fp = GZip
| ".Z" `isSuffixOf` fp = Z
| ".zst" `isSuffixOf` fp = Zstd
| ".lz4" `isSuffixOf` fp = Lz4
| ".xcfgz" `isSuffixOf` fp = GZip
| ".xcfbz2" `isSuffixOf` fp = BZip
| ".cpgz" `isSuffixOf` fp = GZip
| ".cpbz2" `isSuffixOf` fp = BZip
#ifdef BROTLI
| ".br" `isSuffixOf` fp = Brotli
#endif
#ifdef SNAPPY
| ".sz" `isSuffixOf` fp = Snappy
#endif
| otherwise = None -- error "Suffix not supported or invalid"
toFileDecompressor :: Compression -> FilePath -> IO BSL.ByteString
toFileDecompressor Lz4 = lz4DecompressFile
toFileDecompressor x = fmap (toDecompressor x) . BSL.readFile
toDecompressor :: Compression -> BSL.ByteString -> BSL.ByteString
toDecompressor Lzma = Lzma.decompress
toDecompressor Lzip = Lzip.decompress
toDecompressor BZip = BZip.decompress
toDecompressor GZip = GZip.decompress
toDecompressor Z = Zlib.decompress
toDecompressor Zstd = Zstd.decompress
toDecompressor Lz4 = Lz4.decompress
#ifdef BROTLI
toDecompressor Brotli = Br.decompress
#endif
#ifdef SNAPPY
toDecompressor Snappy = Snappy.decompress
#endif
toDecompressor None = id
check :: Compression -> BSL.ByteString -> IO ()
check = (forceBSL .) . toDecompressor
levelGuard :: (Int, Int) -> Int -> Int
levelGuard (min', max') lvl | lvl < min' || lvl > max' =
error ("Invalid compression level. Compression must be between " ++ show min' ++ " and " ++ show max')
| otherwise = lvl
toFileCompressor :: Compression -> CompressionLevel -> FilePath -> IO BSL.ByteString
toFileCompressor Lzip lvl = Lzip.compressFileLevel (toEnum $ toInt Lzip lvl)
toFileCompressor x lvl = fmap (toCompressor x lvl Nothing) . BSL.readFile
gzipCompression :: Int -> GZip.CompressParams
gzipCompression lvl = GZip.defaultCompressParams { GZip.compressLevel = GZip.compressionLevel lvl }
zlibCompression :: Int -> Zlib.CompressParams
zlibCompression lvl = Zlib.defaultCompressParams { Zlib.compressLevel = Zlib.compressionLevel lvl }
lzmaCompression :: Int -> Lzma.CompressParams
lzmaCompression lvl = Lzma.defaultCompressParams { Lzma.compressLevel = toEnum lvl }
#ifdef BROTLI
brotliCompression :: Int -> Br.CompressParams
brotliCompression lvl = Br.defaultCompressParams { Br.compressLevel = toEnum lvl }
#endif
toInt :: Compression -> CompressionLevel -> Int
#ifdef SNAPPY
toInt Snappy _ = undefined
#endif
#ifdef BROTLI
toInt Brotli Fastest = fromEnum (minBound :: Br.CompressionLevel)
toInt Brotli (Custom i) = levelGuard (0,11) i
toInt Brotli _ = fromEnum (maxBound :: Br.CompressionLevel)
#endif
toInt Zstd Best = Zstd.maxCLevel
toInt Zstd Fastest = 1
toInt Zstd (Custom i) = levelGuard (1, Zstd.maxCLevel) i
toInt Zstd Default = 3
toInt Lzip Best = fromEnum (maxBound :: Lzip.CompressionLevel)
toInt Lzip Fastest = fromEnum (minBound :: Lzip.CompressionLevel)
toInt Lzip (Custom i) = levelGuard (0, 9) i
toInt Lzip Default = 6
toInt Lzma Best = 9
toInt Lzma Fastest = 0
toInt Lzma (Custom i) = levelGuard (0, 9) i
toInt Lzma Default = 6
toInt BZip Best = 9
toInt BZip Fastest = 1
toInt BZip (Custom i) = i
toInt BZip Default = 7
toInt GZip Best = 9
toInt GZip Fastest = 0
toInt GZip (Custom i) = i
toInt GZip Default = 6
toInt Z Best = 9
toInt Z Fastest = 0
toInt Z (Custom i) = i
toInt Z Default = 6
toInt Lz4 Best = Lz4.lZ4HCClevelMax
toInt Lz4 Fastest = 0
toInt Lz4 (Custom i) = levelGuard (0, Lz4.lZ4HCClevelMax) i
toInt Lz4 Default = 0 -- 1?
toInt None _ = error "Internal error."
toCompressor :: Compression -> CompressionLevel -> Maybe Int -> BSL.ByteString -> BSL.ByteString
toCompressor Lzma lvl _ = Lzma.compressWith (lzmaCompression $ toInt Lzma lvl)
toCompressor Lzip lvl (Just sz) = flip (Lzip.compressWithSz (toEnum $ toInt Lzip lvl)) sz
toCompressor Lzip _ _ = error "Internal error."
toCompressor BZip lvl _ = BZip.compressWith (fromIntegral $ toInt BZip lvl) 30
toCompressor GZip lvl _ = GZip.compressWith (gzipCompression $ toInt GZip lvl)
toCompressor Z lvl _ = Zlib.compressWith (zlibCompression $ toInt Z lvl)
toCompressor Zstd lvl _ = Zstd.compress (toInt Zstd lvl)
toCompressor Lz4 lvl _ = Lz4.compressSz (toInt Lz4 lvl)
#ifdef BROTLI
toCompressor Brotli lvl _ = Br.compressWith (brotliCompression $ toInt Brotli lvl)
#endif
#ifdef SNAPPY
toCompressor Snappy _ _ = Snappy.compress
#endif
toCompressor None _ _ = id
fileSize :: FilePath -> IO Integer
fileSize fp = withFile fp ReadMode hFileSize
lz4DecompressFile :: FilePath -> IO BSL.ByteString
lz4DecompressFile fp = do
fSz <- fileSize fp
let f = if fSz <= 32 * 1024
then Lz4.decompressBufSz (32 * 1024)
else Lz4.decompressBufSz (128 * 1024)
f <$> BSL.readFile fp
forceBSL :: BSL.ByteString -> IO ()
forceBSL bsl = last (BSL.toChunks bsl) `seq` mempty