packages feed

snappy-0.2.0.3: Codec/Compression/Snappy/Lazy.hsc

{-# LANGUAGE BangPatterns, EmptyDataDecls, ForeignFunctionInterface #-}

-- |
-- Module:      Codec.Compression.Snappy
-- Copyright:   © 2011 MailRank, Inc.
-- License:     Apache
-- Maintainer:  ARJANEN Loïc Jean David <ljd@luigiscorner.mu>
-- Stability:   experimental
-- Portability: portable
--
-- This module provides fast, pure zero-copy compression and
-- decompression of lazy 'ByteString' data using the Snappy format.
--
-- Although these functions operate on lazy 'ByteString's, they
-- consume the data /strictly/: they do not produce any output until
-- they have consumed all of the input, and they produce the output in
-- a single large chunk.
--
-- If your data is already in the form of a lazy 'ByteString', it is
-- likely more efficient to use these functions than to convert your
-- data to and from strict ByteStrings, as you can avoid the
-- additional allocation and copying that would entail.

module Codec.Compression.Snappy.Lazy
    (
      compress
    , decompress
    ) where

#include "hs_snappy.h"

import Codec.Compression.Snappy.Internal (check, maxCompressedLength)
import Control.Exception (bracket)
import Data.ByteString.Internal hiding (ByteString(BS))
import Data.ByteString.Lazy.Internal (ByteString(..))
import Data.Word (Word8, Word32)
import Foreign.C.Types (CInt(..), CSize(..))
import Foreign.ForeignPtr (touchForeignPtr, withForeignPtr, newForeignPtr_)
import Foreign.Marshal.Alloc (alloca)
import Foreign.Marshal.Array (withArray)
import Foreign.Marshal.Utils (with)
import Foreign.Ptr (Ptr, plusPtr)
import Foreign.Storable (Storable(..))
import System.IO.Unsafe (unsafePerformIO)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L

newtype BS = BS B.ByteString

data BSSource

instance Storable BS where
    sizeOf _    = (#size struct BS)
    alignment _ = alignment (undefined :: Ptr CInt)
    poke ptr (BS (PS fp off len)) = withForeignPtr fp $ \p -> do
      (#poke struct BS, ptr) ptr (p `plusPtr` off)
      (#poke struct BS, off) ptr (0::CSize)
      (#poke struct BS, len) ptr len
    {-# INLINE poke #-}
    peek p = do
      pointer <- (#peek struct BS, ptr) p
      foreignPointer <- newForeignPtr_ pointer
      offset <- (#peek struct BS, off) p
      peekedLength <- (#peek struct BS, len) p
      return (BS (PS foreignPointer offset peekedLength))
    {-# INLINE peek #-}

-- | Compress data into the Snappy format.
compress :: ByteString -> ByteString
compress bs = unsafePerformIO . withChunks bs $ \chunkPtr numChunks len -> do
  let dlen0 = maxCompressedLength len
  dfp <- mallocByteString dlen0
  withForeignPtr dfp $ \dptr -> do
    with (fromIntegral dlen0) $ \dlenPtr -> do
      c_CompressChunks chunkPtr (fromIntegral numChunks)
                       (fromIntegral len) dptr dlenPtr
      dlen <- fromIntegral `fmap` peek dlenPtr
      if dlen == 0
        then return Empty
        else return (Chunk (PS dfp 0 dlen) Empty)

-- | Decompress data in the Snappy format.
--
-- If the input is not compressed or is corrupt, an exception will be
-- thrown.
decompress :: ByteString -> ByteString
decompress bs = unsafePerformIO . withChunks bs $ \chunkPtr numChunks len ->
  bracket (c_NewSource chunkPtr (fromIntegral numChunks) (fromIntegral len))
          c_DeleteSource $ \srcPtr -> do
    alloca $ \dlenPtr -> do
      check "Lazy.decompress" $ c_GetUncompressedLengthChunks srcPtr dlenPtr
      dlen <- fromIntegral `fmap` peek dlenPtr
      if dlen == 0
        then return L.empty
        else do
          dfp <- mallocByteString dlen
          withForeignPtr dfp $ \dptr -> do
            check "Lazy.decompress" $ c_UncompressChunks srcPtr dptr
            return (Chunk (PS dfp 0 dlen) Empty)

withChunks :: ByteString -> (Ptr BS -> Int -> Int -> IO a) -> IO a
withChunks bs act = do
  let len = fromIntegral (L.length bs)
  let chunks = L.toChunks bs
  r <- withArray (map BS chunks) $ \chunkPtr ->
       act chunkPtr (length chunks) len
  foldr (\(PS fp _ _) _ -> touchForeignPtr fp) (return ()) chunks
  return r

foreign import ccall unsafe "hs_snappy.h _hsnappy_CompressChunks"
    c_CompressChunks :: Ptr BS -> CSize -> CSize -> Ptr Word8 -> Ptr CSize
                     -> IO ()

foreign import ccall unsafe "hs_snappy.h _hsnappy_NewSource"
    c_NewSource :: Ptr BS -> CSize -> CSize -> IO (Ptr BSSource)

foreign import ccall unsafe "hs_snappy.h _hsnappy_DeleteSource"
    c_DeleteSource :: Ptr BSSource -> IO ()

foreign import ccall unsafe "hs_snappy.h _hsnappy_UncompressChunks"
    c_UncompressChunks :: Ptr BSSource -> Ptr Word8 -> IO Int

foreign import ccall unsafe "hs_snappy.h _hsnappy_GetUncompressedLengthChunks"
    c_GetUncompressedLengthChunks :: Ptr BSSource -> Ptr Word32 -> IO Int