module Development.Bake.Core.Message(
Message(..), Ping(..), Question(..), Answer(..),
sendMessage, messageToInput, messageFromInput, questionToOutput
) where
import Development.Bake.Core.Type
import General.Web
import General.BigString
import Control.Applicative
import Control.Monad
import Control.DeepSeq
import Data.Aeson hiding (Success)
import System.Time.Extra
import Safe
import qualified Data.ByteString.Lazy.Char8 as LBS
import Prelude
data Message
= SetState Author State
| AddPatch Author Patch
| DelPatch Patch
| Requeue
| Pause
| Unpause
| AddSkip Author Test
| DelSkip Test
| Pinged Ping
| Finished {question :: Question, answer :: Answer}
deriving Show
instance NFData Message where
rnf (AddPatch x y) = rnf x `seq` rnf y
rnf (DelPatch x) = rnf x
rnf Requeue = ()
rnf (SetState x y) = rnf x `seq` rnf y
rnf Pause = ()
rnf Unpause = ()
rnf (AddSkip x y) = rnf x `seq` rnf y
rnf (DelSkip x) = rnf x
rnf (Pinged x) = rnf x
rnf (Finished x y) = rnf x `seq` rnf y
data Question = Question
{qCandidate :: (State, [Patch])
,qTest :: Maybe Test
,qThreads :: Int
,qClient :: Client
}
deriving (Show,Eq,Ord)
instance NFData Question where
rnf (Question a b c d) = rnf (a,b,c,d)
data Answer = Answer
{aStdout :: BigString
,aDuration :: Maybe Seconds
,aTests :: [Test]
,aSuccess :: Bool
}
deriving Show
instance NFData Answer where
rnf (Answer a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d
data Ping = Ping
{pClient :: Client
,pAuthor :: Author
,pProvide :: [String]
,pMaxThreads :: Int
,pNowThreads :: Int
}
deriving (Show,Eq)
instance NFData Ping where
rnf (Ping a b c d e) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d `seq` rnf e
instance ToJSON Question where
toJSON Question{..} = object
["candidate" .= toJSONCandidate qCandidate
,"test" .= qTest
,"threads" .= qThreads
,"client" .= qClient]
instance FromJSON Question where
parseJSON (Object v) = Question <$>
(fromJSONCandidate =<< (v .: "candidate")) <*> (v .: "test") <*> (v .: "threads") <*> (v .: "client")
parseJSON _ = mzero
toJSONCandidate (s, ps) = object ["state" .= s, "patches" .= ps]
fromJSONCandidate (Object v) = (,) <$> (v .: "state") <*> (v .: "patches")
fromJSONCandidate _ = mzero
messageToInput :: Message -> Input
messageToInput (AddPatch author patch) = Input ["api","add"] [("author",author),("patch",fromPatch patch)] []
messageToInput (DelPatch patch) = Input ["api","del"] [("patch",fromPatch patch)] []
messageToInput Requeue = Input ["api","requeue"] [] []
messageToInput (SetState author state) = Input ["api","set"] [("author",author),("state",fromState state)] []
messageToInput Pause = Input ["api","pause"] [] []
messageToInput Unpause = Input ["api","unpause"] [] []
messageToInput (AddSkip author test) = Input ["api","addskip"] [("author",author),("test",fromTest test)] []
messageToInput (DelSkip test) = Input ["api","delskip"] [("test",fromTest test)] []
messageToInput (Pinged Ping{..}) = Input ["api","ping"]
([("client",fromClient pClient),("author",pAuthor)] ++
[("provide",x) | x <- pProvide] ++
[("maxthreads",show pMaxThreads),("nowthreads",show pNowThreads)]) []
messageToInput (Finished Question{..} Answer{..}) = Input ["api","finish"] []
[("state", bigStringFromString $ fromState $ fst qCandidate)
,("patch", bigStringFromString $ unlines $ map fromPatch $ snd qCandidate)
,("test", bigStringFromString $ maybe "" fromTest qTest)
,("threads", bigStringFromString $ show qThreads)
,("client", bigStringFromString $ fromClient qClient)
,("stdout", aStdout)
,("duration", bigStringFromString $ maybe "" show aDuration)
,("tests", bigStringFromString $ unlines $ map fromTest aTests)
,("success", bigStringFromString $ show aSuccess)]
messageFromInput :: Input -> Either String Message
messageFromInput (Input [msg] args body)
| msg == "add" = AddPatch <$> str "author" <*> (toPatch <$> str "patch")
| msg == "del" = DelPatch <$> (toPatch <$> str "patch")
| msg == "addskip" = AddSkip <$> str "author" <*> (toTest <$> str "test")
| msg == "delskip" = DelSkip <$> (toTest <$> str "test")
| msg == "requeue" = pure Requeue
| msg == "set" = SetState <$> str "author" <*> (toState <$> str "state")
| msg == "pause" = pure Pause
| msg == "unpause" = pure Unpause
| msg == "ping" = Pinged <$> (Ping <$> (toClient <$> str "client") <*>
str "author" <*> strs "provide" <*> int "maxthreads" <*> int "nowthreads")
where strs x = Right $ map snd $ filter ((==) x . fst) args
str x | Just v <- lookup x args = Right v
| otherwise = Left $ "Missing field " ++ show x ++ " from " ++ show msg
int x = readNote "messageFromInput, expecting Int" <$> str x
messageFromInput (Input [msg] args body)
| msg == "finish" = do
let f x = case lookup x body of Nothing -> Left $ "Missing field " ++ show x ++ " from " ++ show (map fst body); Just x -> Right x
state <- toState . bigStringToString <$> f "state"
patch <- map toPatch . lines . filter (/= '\r') . bigStringToString <$> f "patch"
qTest <- (\x -> if null x then Nothing else Just $ toTest x) . bigStringToString <$> f "test"
qThreads <- read . bigStringToString <$> f "threads"
qClient <- toClient . bigStringToString <$> f "client"
aStdout <- f "stdout"
aDuration <- (\x -> if null x then Nothing else Just $ read x) . bigStringToString <$> f "duration"
aTests <- map toTest . lines . filter (/= '\r') . bigStringToString <$> f "tests"
aSuccess <- read . bigStringToString <$> f "success"
return $ Finished Question{qCandidate=(state,patch),..} Answer{..}
messageFromInput (Input msg args body) = Left $ "Invalid API call, got " ++ show msg
questionToOutput :: Maybe Question -> Output
questionToOutput = OutputString . LBS.unpack . encode
sendMessage :: (Host,Port) -> Message -> IO (Maybe Question)
sendMessage hp msg = do
res <- send hp $ messageToInput msg
return $ decode res