packages feed

spike-0.2: src/Commands.hs

{-# LANGUAGE DeriveDataTypeable, ExistentialQuantification #-}

module Commands where

import Datatypes

import Control.Concurrent
import Control.Concurrent.STM
import Control.Exception
import Data.Global
import Data.IORef
import Data.Typeable

-- | commands channel. base implementations only do some logging, they need to be replaced with proper implementations when appropriate.
commandChannelVar :: IORef CommandControl
commandChannelVar = declareIORef "spike::global::commandChannel" emptyCommandControl

data CommandControl = CommandControl { sendCommand_ :: SomeCommand -> IO ()
                                     , registerListener_ :: (SomeCommand -> IO ()) -> IO () 
                                     } deriving Typeable

data SomeCommand = forall e . Command e => SomeCommand e deriving Typeable

instance Show SomeCommand where
    showsPrec p (SomeCommand e) = showsPrec p e

-- | Command class. Design copied after System.Exception.Exception class
class (Typeable c, Show c) => Command c where
    toCommand :: c -> SomeCommand
    fromCommand :: SomeCommand -> Maybe c

    toCommand = SomeCommand
    fromCommand (SomeCommand c) = Data.Typeable.cast c

sendCommand :: Command c => c -> IO ()
sendCommand c = do
  cc <- readIORef commandChannelVar
  sendCommand_ cc (toCommand c)

registerListener :: (Command c) => (c -> IO ()) -> IO ()
registerListener l = do
  cc <- readIORef commandChannelVar
  registerListener_ cc (\ c -> case fromCommand c of
                                 Nothing -> return ()
                                 Just x -> l x)

emptyCommandControl = CommandControl
                       (\ _ -> print "(default) sendCommand: command ignored")
                       (\ _ -> print "(default) registerListener: listener ignored")

setupCommandControl :: IO ()
setupCommandControl = do
  commandChan <- newChan
  listeners <- newTVarIO []

  let send c = writeChan commandChan c      
      register l = atomically $ do
                     ls <- readTVar listeners
                     writeTVar listeners (l:ls)

      handleCommand c = do
        ls <- readTVarIO listeners
        let logException c e = putStrLn $ "Exception while handling command: " ++ show c ++ " " ++ show (e :: SomeException)
        mapM_ (\ handler -> forkIO (Control.Exception.catch (handler c) (logException c))) ls

  forkIO $ do
    commands <- getChanContents commandChan
    mapM_ handleCommand commands

  let cc = CommandControl send register
  writeIORef commandChannelVar cc
  return ()

-- commands
data ViewPageCommand = ViewPageCommand PageID deriving (Typeable, Show) -- which page
data NewTopLevelPageCommand = NewTopLevelPageCommand URL deriving (Typeable, Show) -- url
data NewChildPageCommand = NewChildPageCommand PageID URL deriving (Typeable, Show) -- parent, url
data ClosePageCommand = ClosePageCommand PageID deriving (Typeable, Show) -- which page

instance Command ViewPageCommand where
instance Command NewChildPageCommand where
instance Command NewTopLevelPageCommand where
instance Command ClosePageCommand where