{- | Module : Web.Handler Description : Application-specific handler functions. Copyright : (c) 2011 Cedric Staub, 2012 Benedikt Schmidt License : GPL-3 Maintainer : Cedric Staub Stability : experimental Portability : non-portable -} {-# LANGUAGE OverloadedStrings, QuasiQuotes, TypeFamilies, FlexibleContexts, 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, thyName, -- lName, -- lookupLemma, addLemma, removeLemma, openTheory, mapProverProof, sorryProver, autoProver, cutOnAttackDFS, prettyClosedTheory, prettyOpenTheory -- 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.Label import Data.String (fromString) -- 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 Text.Blaze.Html5 (toHtml) import Control.Monad import Control.Monad.IO.Class -- import Control.Monad.IO.Control import Control.Monad.Trans.Control import Control.Applicative import Control.Concurrent import Control.DeepSeq import qualified Control.Exception.Lifted 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 :: TheoryIdx -> Handler (Maybe TheoryInfo) getTheory idx = do yesod <- getYesod liftIO $ withMVar (theoryVar yesod) $ return . M.lookup idx -- | Store a theory, return index. putTheory :: Maybe TheoryInfo -- ^ Index of parent theory -> Maybe TheoryOrigin -- ^ Origin of this theory -> ClosedTheory -- ^ The new closed theory -> Handler 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 :: TheoryIdx -> Handler () 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 :: Handler TheoryMap getTheories = do yesod <- getYesod liftIO $ withMVar (theoryVar yesod) return -- | Modify a theory in the map of theories. adjTheory :: TheoryIdx -> (TheoryInfo -> TheoryInfo) -> Handler () 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 :: T.Text -- ^ Request path -> ThreadId -- ^ Thread ID -> Handler () 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 :: T.Text -- ^ Request path -> Handler () 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 :: T.Text -- ^ Request path -> Handler (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 :: MonadBaseControl IO m => String -> m a -> m a traceExceptions info = E.handle handler where handler :: MonadBaseControl IO m => E.SomeException -> m a handler e = trace (info ++ ": exception `" ++ show e ++ "'") $ E.throwIO e -- | Helper functions for generating JSON reponses. jsonResp :: JsonResponse -> GHandler m WebUI RepJson jsonResp = return . RepJson . toContent . 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 => IO a -> Handler (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 :: TheoryIdx -> (TheoryInfo -> Handler a) -> Handler 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 ((result, widget), enctype) <- runFormPost formlet case result of FormMissing -> do RepHtml content <- ajaxLayout (template widget enctype) jsonResp $ JsonHtml title content FormFailure _ -> jsonResp $ JsonAlert "Missing fields in form. Please fill out all required fields." FormSuccess ret -> lift (success ret) -} -- | Modify a theory, redirect if successful. modifyTheory :: TheoryInfo -- ^ Theory to modify -> (ClosedTheory -> IO (Maybe ClosedTheory)) -- ^ Function to apply -> (ClosedTheory -> TheoryPath) -- ^ Compute the new path -> JsonResponse -- ^ Response on failure -> Handler Value modifyTheory ti f fpath errResponse = do 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 (fpath thy)) 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 = do theories <- getTheories defaultLayout $ do setTitle "Welcome to the Tamarin prover" addWidget (rootTpl theories) data File = File T.Text deriving Show postRootR :: Handler RepHtml postRootR = do result <- lookupFile "uploadedTheory" case result of Nothing -> setMessage "Post request failed." Just fileinfo -> do yesod <- getYesod closedThy <- liftIO $ 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) -- | Show overview over theory (framed layout). getOverviewR :: TheoryIdx -> TheoryPath -> Handler RepHtml getOverviewR idx path = withTheory idx $ \ti -> do renderF <- getUrlRender defaultLayout $ do overview <- liftIO $ overviewTpl renderF ti path 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 . 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 = do renderUrl <- getUrlRender jsonValue <- withTheory idx (go renderUrl path) return $ RepJson $ toContent 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) (\thy -> nextSmartThyPath thy (TheoryProof lemma proofPath)) (JsonAlert "Sorry, but the prover failed on the selected method!") -- -- Handle generic paths by trying to render them -- go renderUrl _ ti = do let title = T.pack $ titleThyPath (tiTheory ti) path let html = T.pack $ renderHtmlDoc $ htmlThyPath renderUrl ti path return $ responseToJson (JsonHtml title (toContent html)) -- | Run the autoprover on a given proof path. getAutoProverR :: TheoryIdx -> TheoryPath -> Handler RepJson getAutoProverR idx path = do jsonValue <- withTheory idx (go path) return $ RepJson $ toContent jsonValue where go (TheoryProof lemma proofPath) ti = modifyTheory ti (\thy -> return $ applyProverAtPath thy lemma proofPath (mapProverProof cutOnAttackDFS autoProver)) (\thy -> nextSmartThyPath thy path) (JsonAlert "Sorry, but the autoprover failed on given proof step!") go _ _ = return . responseToJson $ JsonAlert "Can't run autoprover on the given theory path!" {- -- | 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 return [hamlet|

Theory information