hpaste-0.3: PasteState.hs
-----------------------------------------------------------------------------
-- |
-- Module : PasteState
-- Copyright : (c) Eric Mertens 2007
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : emertens@gmail.com
-- Stability : unstable
-- Portability : portable
--
-----------------------------------------------------------------------------
--
-- The basic state of the paste server
--
module PasteState (
currentId
, PasteState
, Entry
, entryNick
, entryTitle
, entryContent
, entryTime
, getEntries
, storeEntry
, storeAnnotation
, allEntries
, newEntry
, TimeStamp
, gzip
, gunzip
) where
import HAppS (StartState(..), Serialize(..))
import qualified Data.Sequence as S
import qualified Data.Foldable as F
import Data.Int
import Data.List
import Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as L
import Control.Monad
import Data.Binary
import Codec.Compression.GZip
------------------------------------------------------------------------
newtype GZBytestring = GZ B.ByteString
-- | The PasteState, the state of our system
data PasteState = PasteState !(S.Seq [Entry])
-- | An individual paste
data Entry = MkEntry2
{ entryNick, entryTitle :: !String
, entryContentGz :: !GZBytestring -- a compressed in-memory bytestring
, entryTime :: !Int64 }
-- | Uncompress a content field on the fly
entryContent :: Entry -> B.ByteString
entryContent e = gunzip gz
where GZ gz = entryContentGz e
--
-- Fast serialisation using Data.Binary
--
instance Binary PasteState where
put (PasteState xs) = put xs
get = liftM PasteState get
instance Binary Entry where
put (MkEntry2 nk ti cn tm) = put nk >> put ti >> put cn >> put tm
get = liftM4 MkEntry2 get get get get
-- | Write out a raw compressed bytestring
instance Binary GZBytestring where
put (GZ b) = put b
get = liftM GZ get
-- migration:
-- get = do b <- get ; return (GZ (gzip b))
------------------------------------------------------------------------
-- | Compress a strict ByteString
gzip :: B.ByteString -> B.ByteString
gzip = B.concat . L.toChunks . compress . L.fromChunks . (:[])
-- | Uncompress a strict ByteString
gunzip :: B.ByteString -> B.ByteString
gunzip = B.concat . L.toChunks . decompress . L.fromChunks . (:[])
------------------------------------------------------------------------
-- | Serialisation of the server state
instance StartState PasteState where
startStateM = return $ PasteState $ S.empty
instance Serialize PasteState where
typeString _ = "PasteState_0"
-- Compress everything. Currently has to go via String :(
encodeFPS a = return . L.toChunks . compress . encode $ a
encodeStringM a = return . L.unpack . compress . encode $ a
decodeStringM s = L.length ps `seq` return (decode (decompress ps), "")
where ps = L.pack s
------------------------------------------------------------------------
-- | A convenient alias for time
type TimeStamp = Int64
------------------------------------------------------------------------
-- | Build a new entry. Shallow wrapper over the Entry constructor
-- Compreses the input bytestring using gzip.
newEntry :: String -> String -> ByteString -> TimeStamp -> Entry
newEntry nick title content t = MkEntry2 nick title (GZ contentgz) t
where contentgz = gzip . B.filter (/='\r') $ content
-- | The current user id (this is a unique supply..)
currentId :: PasteState -> Int
currentId (PasteState s) = fromIntegral $ S.length s
allEntries :: PasteState -> [(Int, [Entry])]
allEntries (PasteState s) = Prelude.zip [n,n-1..] $ F.toList $ S.reverse s
where n = S.length s - 1
getEntries :: Int -> PasteState -> [Entry]
getEntries n (PasteState s) = S.index s n
storeEntry :: Entry -> PasteState -> PasteState
storeEntry e (PasteState s) = PasteState $ (S.|>) s [e]
storeAnnotation :: Int -> Entry -> PasteState -> PasteState
storeAnnotation n e (PasteState s)
= PasteState $ S.update n (S.index s n ++ [e]) s