module System.IO.Uniform.Streamline (
Streamline,
withClient,
withServer,
withTarget,
inStreamlineCtx,
peelStreamlineCtx,
closeTarget,
send,
send',
recieveLine,
recieveLine',
recieveN,
recieveN',
runAttoparsec,
runAttoparsecAndReturn,
runScanner,
runScanner',
scan,
scan',
recieveTill,
recieveTill',
startTls,
isSecure,
setTimeout,
echoTo,
setEcho
) where
import System.IO (stdout, Handle)
import qualified System.IO.Uniform as S
import qualified System.IO.Uniform.Network as N
import qualified System.IO.Uniform.Std as Std
import System.IO.Uniform (UniformIO, SomeIO(..), TlsSettings)
import System.IO.Uniform.Streamline.Scanner
import Data.Default.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Interruptible
import Control.Monad.Trans.Control
import Control.Monad (ap, liftM)
import Control.Monad.Base
import Control.Monad.IO.Class
import System.IO.Error
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import Data.Word8 (Word8)
import Data.IP (IP)
import qualified Data.Attoparsec.ByteString as A
data StreamlineState = StreamlineState {str :: SomeIO, timeout :: Int, buff :: ByteString, isEOF :: Bool, echo :: Maybe Handle}
instance Default StreamlineState where
def = StreamlineState (SomeIO Std.StdIO) defaultTimeout BS.empty False Nothing
newtype Streamline m a = Streamline {withTarget' :: StreamlineState -> m (a, StreamlineState)}
blockSize :: Int
blockSize = 4096
defaultTimeout :: Int
defaultTimeout = 1000000 * 600
readF :: MonadIO m => StreamlineState -> m ByteString
readF cl = case echo cl of
Just h -> do
l <- liftIO $ S.uRead (str cl) blockSize
liftIO $ BS.hPutStr h "<"
liftIO $ BS.hPutStr h l
return l
Nothing -> liftIO $ S.uRead (str cl) blockSize
writeF :: MonadIO m => StreamlineState -> ByteString -> m ()
writeF cl l = case echo cl of
Just h -> do
liftIO $ BS.hPutStr h ">"
liftIO $ BS.hPutStr h l
liftIO $ S.uPut (str cl) l
Nothing -> liftIO $ S.uPut (str cl) l
withServer :: MonadIO m => IP -> Int -> Streamline m a -> m a
withServer host port f = do
ds <- liftIO $ N.connectTo host port
(ret, _) <- withTarget' f def{str=SomeIO ds}
liftIO $ S.uClose ds
return ret
withClient :: MonadIO m => N.BoundedPort -> (IP -> Int -> Streamline m a) -> m a
withClient port f = do
ds <- liftIO $ N.accept port
(peerIp, peerPort) <- liftIO $ N.getPeer ds
(ret, _) <- withTarget' (f peerIp peerPort) def{str=SomeIO ds}
liftIO $ S.uClose ds
return ret
withTarget :: (Monad m, UniformIO a) => a -> Streamline m b -> m b
withTarget s f = do
(r, _) <- withTarget' f def{str=SomeIO s}
return r
instance Monad m => Monad (Streamline m) where
return x = Streamline $ \cl -> return (x, cl)
a >>= b = Streamline $ \cl -> do
(x, cl') <- withTarget' a cl
withTarget' (b x) cl'
instance Monad m => Functor (Streamline m) where
fmap f m = Streamline $ \cl -> do
(x, cl') <- withTarget' m cl
return (f x, cl')
instance (Functor m, Monad m) => Applicative (Streamline m) where
pure = return
(<*>) = ap
instance MonadTrans Streamline where
lift x = Streamline $ \cl -> do
a <- x
return (a, cl)
instance MonadIO m => MonadIO (Streamline m) where
liftIO = lift . liftIO
send :: MonadIO m => ByteString -> Streamline m ()
send r = Streamline $ \cl -> do
writeF cl r
return ((), cl)
send' :: MonadIO m => LBS.ByteString -> Streamline m ()
send' r = Streamline $ \cl -> do
let dd = LBS.toChunks r
mapM_ (writeF cl) dd
return ((), cl)
runScanner :: MonadIO m => s -> IOScanner s -> Streamline m (ByteString, s)
runScanner state scanner = do
(rt, st) <- runScanner' state scanner
return (LBS.toStrict rt, st)
runScanner' :: MonadIO m => s -> IOScanner s -> Streamline m (LBS.ByteString, s)
runScanner' state scanner = Streamline $ \d ->
do
(tx, st, d') <- in_scan d state
return ((LBS.fromChunks tx, st), d')
where
in_scan d st
| isEOF d = eofError "System.IO.Uniform.Streamline.scan'"
| BS.null (buff d) = do
dt <- readF d
if BS.null dt
then return ([], st, d{isEOF=True})
else in_scan d{buff=dt} st
| otherwise = case sscan scanner st 0 (BS.unpack . buff $ d) of
AllInput st' -> do
(tx', st'', d') <- in_scan d{buff=""} st'
return (buff d:tx', st'', d')
SplitAt n st' -> let
(r, i) = BS.splitAt n (buff d)
in return ([r], st', d{buff=i})
sscan :: (s -> Word8 -> IOScannerState s) -> s -> Int -> [Word8] -> ScanResult s
sscan _ s0 _ [] = AllInput s0
sscan s s0 i (w:ww) = case s s0 w of
Finished -> SplitAt i s0
LastPass s1 -> SplitAt (i+1) s1
Running s1 -> sscan s s1 (i+1) ww
data ScanResult s = SplitAt Int s | AllInput s
scan :: MonadIO m => s -> IOScanner s -> Streamline m ByteString
scan state scanner = fst <$> runScanner state scanner
scan' :: MonadIO m => s -> IOScanner s -> Streamline m LBS.ByteString
scan' state scanner = fst <$> runScanner' state scanner
recieveLine :: MonadIO m => Streamline m ByteString
recieveLine = recieveTill "\n"
recieveLine' :: MonadIO m => Streamline m LBS.ByteString
recieveLine' = recieveTill' "\n"
recieveN :: MonadIO m => Int -> Streamline m ByteString
recieveN n = LBS.toStrict <$> recieveN' n
recieveN' :: MonadIO m => Int -> Streamline m LBS.ByteString
recieveN' n | n <= 0 = return ""
| otherwise = Streamline $ \cl ->
do
(tt, cl') <- recieve cl n
return (LBS.fromChunks tt, cl')
where
recieve d b
| isEOF d = eofError "System.IO.Uniform.Streamline.recieveN"
| BS.null . buff $ d = do
dt <- readF d
recieve d{buff=dt}{isEOF=BS.null dt} b
| b <= (BS.length . buff $ d) = let
(r, dt) = BS.splitAt b $ buff d
in return ([r], d{buff=dt})
| otherwise = do
(r, d') <- recieve d{buff=""} $ b (BS.length . buff $ d)
return (buff d : r, d')
recieveTill :: MonadIO m => ByteString -> Streamline m ByteString
recieveTill t = LBS.toStrict <$> recieveTill' t
recieveTill' :: MonadIO m => ByteString -> Streamline m LBS.ByteString
recieveTill' = recieve . BS.unpack
where
recieve t' = scan' [] (textScanner t')
startTls :: MonadIO m => TlsSettings -> Streamline m ()
startTls st = Streamline $ \cl -> do
ds' <- liftIO $ S.startTls st $ str cl
return ((), cl{str=SomeIO ds'}{buff=""})
runAttoparsecAndReturn :: MonadIO m => A.Parser a -> Streamline m (ByteString, Either String a)
runAttoparsecAndReturn p = Streamline $ \cl ->
if isEOF cl
then eofError "System.IO.Uniform.Streamline.runAttoparsecAndReturn"
else do
let c = A.parse p $ buff cl
(cl', i, a) <- liftIO $ continueResult cl c
return ((i, a), cl')
where
continueResult :: StreamlineState -> A.Result a -> IO (StreamlineState, ByteString, Either String a)
continueResult cl c = case c of
A.Fail i _ msg -> return (cl{buff=i}, BS.take (BS.length (buff cl) BS.length i) (buff cl), Left msg)
A.Done i r -> return (cl{buff=i}, BS.take (BS.length (buff cl) BS.length i) (buff cl), Right r)
A.Partial c' -> do
d <- readF cl
let cl' = cl{buff=BS.append (buff cl) d}{isEOF=BS.null d}
continueResult cl' (c' d)
runAttoparsec :: MonadIO m => A.Parser a -> Streamline m (Either String a)
runAttoparsec p = Streamline $ \cl ->
if isEOF cl
then eofError "System.IO.Uniform.Streamline.runAttoparsec"
else do
let c = A.parse p $ buff cl
(cl', a) <- liftIO $ continueResult cl c
return (a, cl')
where
continueResult :: StreamlineState -> A.Result a -> IO (StreamlineState, Either String a)
continueResult cl c = case c of
A.Fail i _ msg -> return (cl{buff=i}, Left msg)
A.Done i r -> return (cl{buff=i}, Right r)
A.Partial c' -> do
d <- readF cl
let eof' = BS.null d
continueResult cl{buff=d}{isEOF=eof'} (c' d)
isSecure :: Monad m => Streamline m Bool
isSecure = Streamline $ \cl -> return (S.isSecure $ str cl, cl)
setTimeout :: Monad m => Int -> Streamline m ()
setTimeout t = Streamline $ \cl -> return ((), cl{timeout=t})
setEcho :: Monad m => Bool -> Streamline m ()
setEcho e = Streamline $ \cl ->
if e then return ((), cl{echo=Just stdout}) else return ((), cl{echo=Nothing})
echoTo :: Monad m => Maybe Handle -> Streamline m ()
echoTo h = Streamline $ \cl -> return ((), cl{echo=h})
eofError :: MonadIO m => String -> m a
eofError msg = liftIO . ioError $ mkIOError eofErrorType msg Nothing Nothing
instance Interruptible Streamline where
type RSt Streamline a = (a, StreamlineState)
resume f (a, st) = withTarget' (f a) st
inStreamlineCtx :: UniformIO io => io -> a -> RSt Streamline a
inStreamlineCtx io a = (a, def{str = SomeIO io})
closeTarget :: MonadIO m => Streamline m ()
closeTarget = Streamline $ \st -> do
liftIO . S.uClose . str $ st
return ((), st)
peelStreamlineCtx :: RSt Streamline a -> (a, SomeIO)
peelStreamlineCtx (a, dt) = (a, str dt)
instance MonadTransControl Streamline where
type StT Streamline a = (a, StreamlineState)
liftWith f = Streamline $ \s ->
liftM (\x -> (x, s))
(f $ \t -> withTarget' t s)
restoreT = Streamline . const
instance MonadBase b m => MonadBase b (Streamline m) where
liftBase = liftBaseDefault
instance MonadBaseControl b m => MonadBaseControl b (Streamline m) where
type StM (Streamline m) a = ComposeSt Streamline m a
liftBaseWith = defaultLiftBaseWith
restoreM = defaultRestoreM