packages feed

EtaMOO-0.2.0.0: src/MOO/Network.hs

{-# LANGUAGE OverloadedStrings #-}

module MOO.Network (
    Point(..)
  , Listener(..)
  , HostName
  , PortNumber
  , value2point
  , point2value
  , createListener
  , listen
  , unlisten
  ) where

import Control.Concurrent.STM (TVar, atomically, modifyTVar)
import Control.Exception (try)
import Control.Monad (when)
import System.IO.Error (isPermissionError)

import MOO.Connection (connectionHandler)
import MOO.Network.Console (createConsoleListener)
import MOO.Network.TCP (HostName, PortNumber, createTCPListener)
import MOO.Object
import {-# SOURCE #-} MOO.Task
import MOO.Types

import qualified Data.Map as M

data Point = Console (TVar World) | TCP (Maybe HostName) PortNumber
           deriving (Eq)

instance Ord Point where
  TCP _ port1 `compare` TCP _ port2 = port1 `compare` port2
  TCP{}       `compare` _           = GT
  Console{}   `compare` Console{}   = EQ
  Console{}   `compare` _           = LT

data Listener = Listener {
    listenerObject        :: ObjId
  , listenerPoint         :: Point
  , listenerPrintMessages :: Bool

  , listenerCancel        :: IO ()
  }

initListener = Listener {
    listenerObject        = systemObject
  , listenerPoint         = TCP Nothing 0
  , listenerPrintMessages = True

  , listenerCancel        = return ()
  }

value2point :: Value -> MOO Point
value2point value = do
  world <- getWorld
  case value of
    Int port      -> return $ TCP (bindAddress world) (fromIntegral port)
    Str "Console" -> Console `fmap` getWorld'
    _             -> raise E_TYPE

point2value :: Point -> Value
point2value point = case point of
  TCP _ port  -> Int (fromIntegral port)
  Console{}   -> Str "Console"

createListener :: TVar World -> ObjId -> Point -> Bool -> IO Listener
createListener world' object point printMessages = do
  let listener = initListener {
          listenerObject        = object
        , listenerPoint         = point
        , listenerPrintMessages = printMessages
        }
      handler = connectionHandler world' object printMessages

  listener <- case point of
    TCP{}     -> createTCPListener     listener handler
    Console{} -> createConsoleListener listener handler

  let canon = listenerPoint listener
  atomically $ modifyTVar world' $ \world -> world {
    listeners = M.insert canon listener (listeners world) }

  return listener

listen :: ObjId -> Point -> Bool -> MOO Point
listen object point printMessages = do
  world <- getWorld
  when (point `M.member` listeners world) $ raise E_INVARG

  world' <- getWorld'
  result <- requestIO $ try $ createListener world' object point printMessages
  case result of
    Left err | isPermissionError err -> raise E_PERM
             | otherwise             -> raise E_QUOTA
    Right listener                   -> return (listenerPoint listener)

unlisten :: Point -> MOO ()
unlisten point = do
  world <- getWorld
  case point `M.lookup` listeners world of
    Just Listener { listenerCancel = cancelListener } -> do
      putWorld world { listeners = M.delete point (listeners world) }
      delayIO cancelListener
    Nothing -> raise E_INVARG