packages feed

titan-1.0.2: src/IOBridge.hs

{-# LANGUAGE ScopedTypeVariables #-}
module IOBridge where

import Control.Applicative
import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.Monad.IfElse
import Data.Bits
import Data.List
import Data.IORef
import Data.Maybe
import Graphics.UI.Gtk
import Network.BSD
import Network.Socket
import System.IO
import Foreign.Ptr

-- | Bridge between the local debugger and the debugging GUI
type IOBridge = IORef IOBridge'

data IOBridge' = IOBridge'
  { yampaSocket :: Maybe YampaHandle }

mkDefaultIOBridge :: IO IOBridge
mkDefaultIOBridge =
  newIORef $ IOBridge' { yampaSocket = Nothing }

-- | Communication Handles
data YampaHandle = YampaHandle
  { commHandle  :: Handle
  , eventHandle :: Handle
  }

-- | Create communication handles to talk to Yampa simulation
--
-- TODO: Make this safe and may return type Maybe
openYampaHandle :: IO YampaHandle -- ^ Handles to communicate with FRP simulation
openYampaHandle = YampaHandle <$> openYampaCommHandle <*> openYampaEventHandle

-- | Open Sync communication channel with FRP simulation

openYampaCommHandle :: IO Handle -- ^ Handle to send commands and receive
                                 --    results from FRP debugger.
openYampaCommHandle = do
  -- Shamelessly taken from RWH

  let hostname = "localhost"
      port     = "8081"
  -- Look up the hostname and port. Either raises an exception or returns a
  -- nonempty list. First element in that list is supposed to be the best
  -- option.
  addrinfos <- getAddrInfo Nothing (Just hostname) (Just port)
  let serveraddr = head addrinfos

  -- Establish a socket for communication
  sock <- socket (addrFamily serveraddr) Stream defaultProtocol

  -- Mark the socket for keep-alive handling since it may be idle
  -- for long periods of time
  setSocketOption sock KeepAlive 1

  -- Connect to server
  connect sock (addrAddress serveraddr)

  -- Make a Handle out of it for convenience
  h <- socketToHandle sock ReadWriteMode

  -- We're going to set buffering to BlockBuffering and then
  -- explicitly call hFlush after each message, below, so that
  -- messages get logged immediately
  hSetBuffering h LineBuffering

  -- Save off the socket, program name, and server address in a handle
  return h

openYampaEventHandle :: IO Handle -- ^ Handle to use for logging
openYampaEventHandle = do
  let hostname = "localhost"
      port     = "8082"
  -- Look up the hostname and port.  Either raises an exception
  -- or returns a nonempty list.  First element in that list
  -- is supposed to be the best option.
  addrinfos <- getAddrInfo Nothing (Just hostname) (Just port)
  let serveraddr = head addrinfos

  -- Establish a socket for communication
  sock <- socket (addrFamily serveraddr) Stream defaultProtocol

  -- Mark the socket for keep-alive handling since it may be idle
  -- for long periods of time
  setSocketOption sock KeepAlive 1

  -- Connect to server
  connect sock (addrAddress serveraddr)

  -- Make a Handle out of it for convenience
  h <- socketToHandle sock ReadWriteMode

  -- We're going to set buffering to BlockBuffering and then
  -- explicitly call hFlush after each message, below, so that
  -- messages get logged immediately
  hSetBuffering h LineBuffering

  -- Save off the socket, program name, and server address in a handle
  return h

stopYampaSocket ioBridgeRef = do
  ioBridge <- readIORef ioBridgeRef
  let mSocket = yampaSocket ioBridge
  awhen mSocket $ \socket -> do
    hClose (commHandle socket)
    let ioBridge' = ioBridge { yampaSocket = Nothing }
    writeIORef ioBridgeRef ioBridge'

startYampaSocket ioBridgeRef = do
  ioBridge <- readIORef ioBridgeRef
  let mSocket = yampaSocket ioBridge
  when (isNothing mSocket) $ do
    handle <- openYampaHandle
    let mSocket'  = Just handle
        ioBridge' = ioBridge { yampaSocket = mSocket' }
    writeIORef ioBridgeRef ioBridge'

sendToYampaSocketSync ioBridgeRef msg =
  catch (sendToYampaSocketSync' ioBridgeRef msg)
        (\(e :: IOException) -> do hPutStrLn stderr ("Send failed when trying to send " ++ msg ++ " with " ++ show e)
                                   return Nothing)

sendToYampaSocketSync' ioBridgeRef msg = do
  ioBridge <- readIORef ioBridgeRef
  whenMaybe (yampaSocket ioBridge) $ \socket -> do
    hPutStrLn stderr ("Debug: Sending " ++ msg)
    hPutStrLn (commHandle socket) msg
    hFlush (commHandle socket)
    waitForInput (commHandle socket) 10000
    s <- hGetLine (commHandle socket)
    hPutStrLn stderr ("Debug: received " ++ s)
    return (Just s)

getFromYampaSocketSync ioBridgeRef =
  catch (getFromYampaSocketSync' ioBridgeRef)
        (\(e :: IOException) -> do hPutStrLn stderr ("Reading failed")
                                   return Nothing)

getFromYampaSocketSync' ioBridgeRef = do
  ioBridge <- readIORef ioBridgeRef
  let mSocket = yampaSocket ioBridge
  whenMaybe (yampaSocket ioBridge) $ \socket ->
    Just <$> hGetLine (commHandle socket)

getFromEventSocketSync ioBridgeRef =
  catch (getFromEventSocketSync' ioBridgeRef)
        (\(e :: IOException) -> do hPutStrLn stderr ("Reading failed " ++ show e)
                                   return Nothing)

getFromEventSocketSync' ioBridgeRef = do
  ioBridge <- readIORef ioBridgeRef
  whenMaybe (yampaSocket ioBridge) $ \socket -> do
    eof <- hIsEOF (eventHandle socket)
    if eof
      then do putStrLn "Got nothing in the event log"
              return Nothing
      else do s <- hGetLine  (eventHandle socket)
              putStrLn $ "Event log got: " ++ show s
              return (Just s)

sendToYampaSocketAsync ioBridgeRef msg =
  catch (sendToYampaSocketAsync' ioBridgeRef msg)
        (\(e :: IOException) -> do hPutStrLn stderr ("Send failed when trying to send " ++ msg)
                                   return ())

sendToYampaSocketAsync' ioBridgeRef msg = do
  ioBridge <- readIORef ioBridgeRef
  let mSocket = yampaSocket ioBridge
  awhen mSocket $ \socket -> do
    hPutStrLn stderr ("Debug: Sending " ++ msg)
    hFlush stderr
    hPutStrLn (commHandle socket) msg

waitForInput handle n = do
  eof <- hIsEOF handle
  when eof $ do
    threadDelay n
    waitForInput handle n

whenMaybe :: Maybe a -> (a -> IO (Maybe b)) -> IO (Maybe b)
whenMaybe Nothing  _ = return Nothing
whenMaybe (Just x) f = f x