packages feed

box-socket-0.1.1: src/Box/TCP.hs

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE DeriveGeneric #-}

module Box.TCP
  ( TCPConfig(..),
    defaultTCPConfig,
    Env(..),
    defaultVersion,
    new,
    close,
    tcpEmitter,
    tcpCommitter,
    tcpBox,
    tcpServer,
    tcpResponder,
    tcpSender,
    tcpStdClient,
    testHarness,
    testResponder,
    testServerSender,
  ) where

import Box
import NumHask.Prelude hiding (handle, check)
import Network.Simple.TCP
import Control.Lens

data TCPConfig
  = TCPConfig
      { host :: Text,
        port :: Text
      }
  deriving (Show, Eq, Generic)

defaultTCPConfig :: TCPConfig
defaultTCPConfig = TCPConfig "127.0.0.1" "3566"
-- ib 7496

-- | an active
data Env
  = Env
      { socket :: Socket,
        sockaddr :: SockAddr,
        ascreendump :: Maybe (Async ()),
        afiledump :: Maybe (Async ())
      }

defaultVersion :: Text
defaultVersion = "11"

-- | Connects to a server
new ::
  -- | Configuration
  TCPConfig ->
  IO Env
new cfg = do
  (sock, sa) <- connectSock (unpack $ host cfg) (unpack $ port cfg)
  pure (Env sock sa Nothing Nothing)

-- | close an Env
close :: Env -> IO ()
close env = do
  closeSock (socket env)
  maybe (pure ()) cancel (ascreendump env)
  maybe (pure ()) cancel (afiledump env)

tcpEmitter :: Socket -> Emitter IO ByteString
tcpEmitter s = Emitter $ recv s 2048

tcpCommitter :: Socket -> Committer IO ByteString
tcpCommitter s = Committer $ \bs -> send s bs *> pure True

tcpBox :: Socket -> Box IO ByteString ByteString
tcpBox s = Box (tcpCommitter s) (tcpEmitter s)

tcpServer :: TCPConfig -> Box IO ByteString ByteString -> IO ()
tcpServer cfg (Box c e) =
  serve HostAny (unpack $ port cfg)
      (\(s,_) -> void $ do
          race
            (glue (tcpCommitter s) e)
            (glue c (tcpEmitter s)))

responder :: (ByteString -> IO ByteString) -> Box IO ByteString ByteString -> IO ()
responder f (Box c e) =
  glue c (mapE (\bs -> Just <$> f bs) e)

--  A server that explicitly responds to client messages.
tcpResponder :: TCPConfig -> (ByteString -> IO ByteString) -> IO ()
tcpResponder cfg f =
  serve HostAny (unpack $ port cfg)
    (\(s,_) -> responder f (Box (tcpCommitter s) (tcpEmitter s)))

tcpSender :: TCPConfig -> Emitter IO ByteString -> IO ()
tcpSender cfg e =
  serve HostAny (unpack $ port cfg)
    (\(s,_) -> glue (tcpCommitter s) e)

tcpStdClient :: TCPConfig -> IO ()
tcpStdClient cfg = do
  (Env s _ _ _) <- new cfg
  void $ concurrently
     (glue o (tcpEmitter s))
     (glue (tcpCommitter s) i)
  where
    o = contramap decodeUtf8 toStdout
    i = fmap encodeUtf8 fromStdin

testHarness :: IO () -> IO ()
testHarness io =
  void $ race
    io
    (cancelQ fromStdin)

cancelQ :: Emitter IO Text -> IO ()
cancelQ e = do
  e' <- emit e
  case e' of
    Just "q" -> pure ()
    Just x -> putStrLn ("badly handled: " <> x)
    Nothing -> pure ()

testResponder :: IO ()
testResponder = testHarness (tcpResponder defaultTCPConfig (pure . ("echo: " <>)))

testServerSender :: IO ()
testServerSender = testHarness $
  tcpSender defaultTCPConfig <$.> (fromListE ["hi!"])