{- | Module : Web.Handler Description : Application-specific handler functions. Copyright : (c) 2011 Cedric Staub License : GPL-3 Maintainer : Cedric Staub Stability : experimental Portability : non-portable -} {-# LANGUAGE OverloadedStrings, QuasiQuotes, TypeFamilies, RankNTypes, TemplateHaskell, CPP #-} module Web.Handler ( getOverviewR , getRootR , postRootR , getTheorySourceR , getTheoryMessageDeductionR , getTheoryVariantsR , getTheoryPathMR , getTheoryPathDR , getTheoryGraphR , getAutoProverR , getDeleteStepR , getKillThreadR , getNextTheoryPathR , getPrevTheoryPathR , getSaveTheoryR , getDownloadTheoryR , getEditTheoryR , postEditTheoryR , getEditPathR , postEditPathR , getUnloadTheoryR , getThreadsR ) where import Theory ( ClosedTheory, lName, thyName, lookupLemma, addLemma, removeLemma, openTheory, mapProverProof, sorryProver, autoProver, cutOnAttackDFS, prettyProof, prettyLemma, prettyClosedTheory, prettyOpenTheory ) import Theory.Parser import Theory.Proof.Sequent.Dot import Web.Types import Web.Hamlet import Web.Theory import Web.Instances () import Web.Settings import Text.PrettyPrint.Html import Yesod.Core import Yesod.Json() import Yesod.Form import Text.Hamlet import Data.Maybe import Data.Aeson import Data.Aeson.Encode (fromValue) import Data.Label import Data.Traversable (traverse) import qualified Data.Map as M import qualified Data.Text as T import qualified Data.Traversable as Tr import qualified Data.ByteString.Lazy.Char8 as BS import Data.Text.Encoding import qualified Blaze.ByteString.Builder as B import Network.HTTP.Types ( urlDecode ) import Control.Monad import Control.Monad.IO.Class import Control.Monad.IO.Control import Control.Applicative import Control.Concurrent import Control.DeepSeq import qualified Control.Exception.Control as E import Control.Exception.Base import qualified Control.Concurrent.Thread as Thread ( forkIO ) import Data.Time.LocalTime import qualified Data.Binary as Bin import System.Directory import Debug.Trace (trace) -- Quasi-quotation syntax changed from GHC 6 to 7, -- so we need this switch in order to support both #if __GLASGOW_HASKELL__ >= 700 #define HAMLET hamlet #else #define HAMLET $hamlet #endif ------------------------------------------------------------------------------ -- Manipulate the state ------------------------------------------------------------------------------ -- | Store theory map in file if option enabled. storeTheory :: WebUI -> TheoryInfo -> TheoryIdx -> IO () storeTheory yesod thy idx = when (autosaveProofstate yesod) $ do let f = workDir yesod++"/"++autosaveSubdir++"/"++show idx++".img" Bin.encodeFile (f++".tmp") thy renameFile (f++".tmp") f -- | Load a theory given an index. getTheory :: MonadIO m => TheoryIdx -> GenericHandler m (Maybe TheoryInfo) getTheory idx = do yesod <- getYesod liftIO $ withMVar (theoryVar yesod) $ return . M.lookup idx -- | Store a theory, return index. putTheory :: MonadIO m => Maybe TheoryInfo -- ^ Index of parent theory -> Maybe TheoryOrigin -- ^ Origin of this theory -> ClosedTheory -- ^ The new closed theory -> GenericHandler m TheoryIdx putTheory parent origin thy = do yesod <- getYesod liftIO $ modifyMVar (theoryVar yesod) $ \theories -> do time <- getZonedTime let idx = if M.null theories then 1 else fst (M.findMax theories) + 1 parentIdx = tiIndex <$> parent parentOrigin = tiOrigin <$> parent newOrigin = parentOrigin <|> origin <|> (Just Interactive) newThy = TheoryInfo idx thy time parentIdx False (fromJust newOrigin) storeTheory yesod newThy idx return (M.insert idx newThy theories, idx) -- | Delete theory. delTheory :: MonadIO m => TheoryIdx -> GenericHandler m () delTheory idx = do yesod <- getYesod liftIO $ modifyMVar_ (theoryVar yesod) $ \theories -> do let theories' = M.delete idx theories -- FIXME: delete from autosave directory? return theories' -- | Get a map of all stored theories. getTheories :: MonadIO m => GenericHandler m TheoryMap getTheories = do yesod <- getYesod liftIO $ withMVar (theoryVar yesod) return -- | Modify a theory in the map of theories. adjTheory :: MonadIO m => TheoryIdx -> (TheoryInfo -> TheoryInfo) -> GenericHandler m () adjTheory idx f = do yesod <- getYesod liftIO $ modifyMVar_ (theoryVar yesod) $ \theories -> case M.lookup idx theories of Just thy -> do let newThy = f thy storeTheory yesod newThy idx return $ M.insert idx newThy theories Nothing -> error "adjTheory: invalid theory index" -- | Debug tracing. dtrace :: WebUI -> String -> a -> a dtrace yesod msg | debug yesod = trace msg | otherwise = id -- | Register a thread for killing. putThread :: MonadControlIO m => T.Text -- ^ Request path -> ThreadId -- ^ Thread ID -> GenericHandler m () putThread str tid = do yesod <- getYesod liftIO $ dtrace yesod msg $ modifyMVar_ (threadVar yesod) $ return . (M.insert str tid) where msg = "Registering thread: " ++ T.unpack str -- | Unregister a thread for killing. delThread :: MonadControlIO m => T.Text -- ^ Request path -> GenericHandler m () delThread str = do yesod <- getYesod liftIO $ dtrace yesod msg $ modifyMVar_ (threadVar yesod) $ return . (M.delete str) where msg = "Deleting thread: " ++ T.unpack str -- | Get a thread for the given request URL. getThread :: MonadIO m => T.Text -- ^ Request path -> GenericHandler m (Maybe ThreadId) getThread str = do yesod <- getYesod liftIO $ dtrace yesod msg $ withMVar (threadVar yesod) $ return . M.lookup str where msg = "Retrieving thread id of: " ++ T.unpack str -- | Get the map of all threads. getThreads :: MonadIO m => GenericHandler m [T.Text] getThreads = do yesod <- getYesod liftIO $ withMVar (threadVar yesod) (return . M.keys) ------------------------------------------------------------------------------ -- Helper functions ------------------------------------------------------------------------------ -- | Print exceptions, if they happen. traceExceptions :: MonadControlIO m => String -> m a -> m a traceExceptions info = E.handle handler where handler :: MonadControlIO m => E.SomeException -> m a handler e = trace (info ++ ": exception `" ++ show e ++ "'") $ liftIO $ E.throw e -- | Helper functions for generating JSON reponses. jsonResp :: Monad m => JsonResponse -> GenericHandler m RepJson jsonResp = return . RepJson . toContent . fromValue . responseToJson responseToJson :: JsonResponse -> Value responseToJson = go where jsonObj key val = object [ key .= val ] go (JsonAlert msg) = jsonObj "alert" $ toJSON msg go (JsonRedirect url) = jsonObj "redirect" $ toJSON url go (JsonHtml title content) = object [ "html" .= contentToJson content , "title" .= title ] contentToJson (ContentBuilder b _) = toJSON $ B.toLazyByteString b contentToJson _ = error "Unsupported content format in json response!" -- | Fully evaluate a value in a thread that can be canceled. evalInThread :: (NFData a, MonadControlIO m) => IO a -> GenericHandler m (Either SomeException a) evalInThread io = do renderF <- getUrlRender maybeRoute <- getCurrentRoute case maybeRoute of Just route -> do let key = renderF route (tid, wait) <- liftIO $ Thread.forkIO $ do x <- io evaluate (rnf x) return x putThread key tid res <- liftIO $ wait delThread key return res Nothing -> Right `liftM` liftIO io -- | Evaluate a handler with a given theory specified by the index, -- return notFound if theory does not exist. withTheory :: MonadIO m => TheoryIdx -> (TheoryInfo -> GenericHandler m a) -> GenericHandler m a withTheory idx handler = do maybeThy <- getTheory idx case maybeThy of Just ti -> handler ti Nothing -> notFound -- | Run a form and provide a JSON response. formHandler :: (HamletValue h, HamletUrl h ~ WebUIRoute, h ~ Widget ()) => T.Text -- ^ The form title -> Form WebUI WebUI a -- ^ The formlet to run -> (Widget () -> Enctype -> Html -> h) -- ^ Template to render form with -> (a -> GenericHandler IO RepJson) -- ^ Function to call on success -> Handler RepJson formHandler title formlet template success = do (result, widget, enctype, nonce) <- runFormPost formlet case result of FormMissing -> do RepHtml content <- ajaxLayout (template widget enctype nonce) jsonResp $ JsonHtml title content FormFailure _ -> jsonResp $ JsonAlert "Missing fields in form. Please fill out all required fields." FormSuccess ret -> liftIOHandler (success ret) -- | Modify a theory, redirect if successful. modifyTheory :: (MonadControlIO m, Functor m) => TheoryInfo -- ^ Theory to modify -> (ClosedTheory -> IO (Maybe ClosedTheory)) -- ^ Function to apply -> JsonResponse -- ^ Response on failure -> GenericHandler m Value modifyTheory ti f errResponse = do -- res <- evalInThread (liftIO $ f (tiTheory ti)) res <- evalInThread (liftIO $ f (tiTheory ti)) case res of Left e -> return (excResponse e) Right Nothing -> return (responseToJson errResponse) Right (Just thy) -> do newThyIdx <- putTheory (Just ti) Nothing thy newUrl <- getUrlRender <*> pure (OverviewR newThyIdx) return . responseToJson $ JsonRedirect newUrl where excResponse e = responseToJson (JsonAlert $ "Last request failed with exception: " `T.append` (T.pack (show e))) ------------------------------------------------------------------------------ -- Handler functions ------------------------------------------------------------------------------ -- | The root handler lists all theories by default, -- or load a new theory if the corresponding form was submitted. getRootR :: Handler RepHtml getRootR = postRootR postRootR :: Handler RepHtml postRootR = do (result, widget, enctype, nonce) <- runFormPost submitForm case result of FormMissing -> return () FormFailure errs -> do mapM_ (liftIO . print) errs setMessage "Loading failed." FormSuccess fileinfo -> do yesod <- getYesod closedThy <- parseThy yesod (BS.unpack $ fileContent fileinfo) void $ putTheory Nothing (Just $ Upload $ T.unpack $ fileName fileinfo) closedThy setMessage "Loaded new theory!" theories <- getTheories defaultLayout $ do setTitle "Welcome to the tamarin prover" addWidget (rootTpl theories widget enctype nonce) where submitForm = fieldsToDivs $ fileField $ FormFieldSettings "Select file:" "Select file" Nothing Nothing -- | Show overview over theory (framed layout). getOverviewR :: TheoryIdx -> Handler RepHtml getOverviewR idx = withTheory idx $ \ti -> defaultLayout $ do overview <- liftIO $ overviewTpl ti TheoryMain setTitle (toHtml $ "Theory: " ++ get thyName (tiTheory ti)) addWidget overview -- | Show source (pretty-printed open theory). getTheorySourceR :: TheoryIdx -> Handler RepPlain getTheorySourceR idx = withTheory idx $ \ti -> return $ RepPlain $ toContent $ prettyRender ti where -- prettyRender = render . prettyOpenTheory . openTheory . tiTheory prettyRender = render . prettyClosedTheory . tiTheory -- | Show variants (pretty-printed closed theory). getTheoryVariantsR :: TheoryIdx -> Handler RepPlain getTheoryVariantsR idx = withTheory idx $ \ti -> return $ RepPlain $ toContent $ prettyRender ti where prettyRender = render . prettyClosedTheory . tiTheory -- | Show variants (pretty-printed closed theory). getTheoryMessageDeductionR :: TheoryIdx -> Handler RepPlain getTheoryMessageDeductionR idx = withTheory idx $ \ti -> return $ RepPlain $ toContent $ prettyRender ti where prettyRender = render . prettyClosedTheory . tiTheory -- | Show a given path within a theory (main view). getTheoryPathMR :: TheoryIdx -> TheoryPath -> Handler RepJson getTheoryPathMR idx path = liftIOHandler $ do jsonValue <- withTheory idx (go path) return $ RepJson $ toContent $ fromValue jsonValue where -- -- Handle method paths by trying to solve the given goal/method -- go (TheoryMethod lemma proofPath i) ti = modifyTheory ti (\thy -> return $ applyMethodAtPath thy lemma proofPath i) (JsonAlert "Sorry, but the prover failed on the selected method!") -- -- Handle generic paths by trying to render them -- go _ ti = do let title = T.pack $ titleThyPath (tiTheory ti) path let html = T.pack $ renderHtmlDoc $ htmlThyPath (tiTheory ti) path return $ responseToJson (JsonHtml title (toContent html)) -- | Show a given path within a theory (debug view). getTheoryPathDR :: TheoryIdx -> TheoryPath -> Handler RepHtml getTheoryPathDR idx path = withTheory idx $ \ti -> ajaxLayout $ do let maybeDebug = htmlThyDbgPath (tiTheory ti) path let maybeWidget = wrapHtmlDoc <$> maybeDebug addWidget [HAMLET|

Theory information