import Directory import Monad import Control.Monad import System.Plugins.Make import System.Exit import System import System.Directory main = do args <- getArgs case args of [] -> do putStrLn "Usage: install-hascat " exitFailure (_:_:_) -> do putStrLn "Usage: install-hascat " exitFailure [d] -> do absD <- canonicalizePath d putStrLn $ "The directory you have selected should be empty.\n" ++ "Do you really want to install hascat to the following directory? (y/n)\n" ++ absD ++ "/" ans <- getLine case ans of "y" -> do setCurrentDirectory d createDirectories createFiles compileFiles putStrLn "Success!" _ -> exitFailure where createDirectories = forM_ directories (\d -> doesDirectoryExist d >>= (flip (when.not) $ createDirectory d)) createFiles = do forM_ haskellFiles (\(fn,fc) -> writeFile (fn ++ ".hs") fc) sf <- staticFiles forM_ sf (\(fn,fc) -> writeFile fn fc) compileFiles = forM_ haskellFiles (\(fn,_) -> (build (fn ++ ".hs") (fn ++ ".o") ["-c", "--make"]) >>= (flip forM) putStrLn) directories :: [FilePath] directories = ["webapps/","webapps/root","webapps/root/static", "webapps/system","webapps/system/static","webapps/cgi-bin","webapps/cgi"] haskellFiles :: [(FilePath,String)] haskellFiles = [("webapps/system/Manager","module Manager where\n\nimport Control.Concurrent.MVar\nimport Control.Monad\nimport Data.List as List\nimport Data.Maybe\nimport Hascat.App\nimport Hascat.Config\nimport Hascat.System.App hiding ( start, stop )\nimport Hascat.System.Controller\nimport Hascat.Toolkit\nimport Network.HTTP\nimport Network.URI\nimport Text.Html hiding ( start ) \nimport Data.ByteString.Lazy as Lazy hiding ( pack, unpack,span )\nimport Data.ByteString.Lazy.Char8 as Lazy ( pack, unpack, span )\nimport qualified Hascat.Protocol as HP\nimport Data.List\nimport Hascat.Toolkit\n\n\nsystemHandler = SystemHandler respond \n\n\nrespond :: AppConfig -> MVar State -> HP.ServletRequest -> IO (Response ByteString)\nrespond config stateVar request = do\n let headers = HP.rqHeaders request\n authMB = lookupHeader HdrAuthorization headers\n case authMB of\n Nothing -> return (getResponse401 (getAppName config))\n Just auth -> do\n configAuth <- readAuth $ getAppRoot config \n if auth == configAuth then\n case uriPath (HP.rqURI request) of\n \"\" -> do\n let inputs = HP.rqInputs request\n -- print inputs --DEBUG\n case lookup \"command\" inputs of\n Nothing -> withMVar stateVar $\n return . getResponse200 . createHtml noHtml\n Just command -> evalCommand stateVar (unpack $ HP.inputValue command) inputs\n appPath ->\n getFileResponse ((show.getAppContextPath) config // appPath)\n (getAppRoot config // \"static\" // appPath)\n Nothing\n else return (getResponse401 \"WallyWorld\")\n\ncreateHtml :: Html -> State -> Html\ncreateHtml infoHtml state@(State _ _ controller) =\n let title = \"Hascat Application Manager\"\n in thehtml <<\n [header <<\n [thetitle << title,\n thelink ! [rel \"stylesheet\",\n href \"hascat.css\",\n thetype \"text/css\"] <<\n noHtml],\n body <<\n [h1 << title,\n infoHtml,\n appsToHtml state]]\n\n\nappsToHtml :: State -> Html\nappsToHtml state =\n tag \"table\" ! [theclass \"b\",\n strAttr \"frame\" \"box\",\n strAttr \"rules\" \"all\"] <<\n [tag \"thead\" << tr <<\n List.map (th <<) [\"Context path\",\n \"Name and Description\",\n \"Running\",\n \"Commands\"],\n tag \"tbody\" << List.map appToHtml (stApps state)]\n\n\nappToHtml :: App -> Html\nappToHtml app =\n let running = isRunning app\n paused = isPaused app\n config = appConfig app\n contextPath = show $ getAppContextPath config\n normal = getAppType config == AppConfig_type_normal\n in tr\n << [td << (if running then anchor ! [href contextPath] << contextPath\n else toHtml contextPath),\n td ! [theclass \"left\"]\n << [toHtml $ getAppName config,\n br,\n thespan ! [theclass \"smaller\"] << getAppDescription config],\n td ! [theclass \"center\"]\n << if running then\n if paused then \"Paused\"\n else \"Yes\" \n else \"No\",\n td ! [theclass \"center\"]\n << thespan ! [theclass \"nowrap\"]\n << List.intersperse (primHtml \" \")\n [createButton \"Pause\" (not paused && running && normal)\n \"pause\" contextPath,\n createButton \"Resume\" (paused && running && normal)\n \"resume\" contextPath,\n createButton \"Stop\" (running && normal)\n \"stop\" contextPath,\n createButton \"Start\" (not running && normal)\n \"start\" contextPath,\n createButton \"Reload\" (not running && normal)\n \"reload\" contextPath,\n createButton \"Undeploy\" (not running && normal)\n \"undeploy\" contextPath]]\n\n\ncreateButton :: String -> Bool -> String -> String -> Html\ncreateButton text enabled command path =\n if enabled then\n anchor ! [href $ \"?command=\" ++ command ++ \"&path=\" ++ path]\n << text\n else\n thespan ! [theclass \"light\"] << text\n\n\ninfoRedirect :: String -> IO (Response ByteString)\ninfoRedirect text = do\n let path = \"/Manager/\" -- TODO\n return $ getResponse303 (path ++ \"?command=info&\" ++ text)\n\n\n\n\nevalCommand :: StateVar -> String -> [(String,HP.Input)] -> IO (Response ByteString)\nevalCommand stateVar command params = do\n let pathMB = convertMaybe ((ContextPath).unpack.HP.inputValue) $ lookup \"path\" params\n case command of\n \"info\" -> info stateVar params\n \"pause\" -> genericModifier stateVar pathMB pauseApp \"paused\"\n \"resume\" -> genericModifier stateVar pathMB resumeApp \"resumed\"\n \"start\" -> genericModifier stateVar pathMB startApp \"started\"\n \"stop\" -> genericModifier stateVar pathMB stopApp \"stopped\"\n \"reload\" -> genericModifier stateVar pathMB reloadApp \"reloaded\"\n \"undeploy\" -> genericModifier stateVar pathMB undeployApp \"undeployed\"\n _ -> infoRedirect (\"text=Unknown command: \" ++ command)\n\n\nconvertMaybe :: (a -> b) -> Maybe a -> Maybe b\nconvertMaybe _ Nothing = Nothing\nconvertMaybe f (Just v) = Just $ f v\n\n\ninfo :: StateVar -> [(String,HP.Input)] -> IO (Response ByteString)\ninfo stateVar params =\n let htmlinfo =\n case lookup \"error\" params of \n Just error -> paragraph ! [theclass \"error\"] << ((unpack.HP.inputValue) error)\n Nothing -> case lookup \"text\" params of\n Just text -> paragraph ! [theclass \"info\"] << ((unpack.HP.inputValue) text)\n Nothing -> noHtml\n in\n withMVar stateVar $\n return .\n getResponse200 .\n createHtml htmlinfo\n\n \ngenericModifier :: StateVar\n -> Maybe ContextPath\n -> (StateVar -> ContextPath -> IO StateVar)\n -> String\n -> IO (Response ByteString)\ngenericModifier _ Nothing _ _ = infoRedirect \"error=Application not found!\"\ngenericModifier stateVar (Just path) action verb =\n catch\n (do action stateVar path\n infoRedirect (\"text=Application at \" ++ (show path) ++ \" \" ++ verb))\n handleError\n\nhandleError :: IOError -> IO (Response ByteString)\nhandleError ex = infoRedirect $ \"error=\"++(show ex)\n\n\nreadAuth :: String -> IO String\nreadAuth appRoot = do\n file <- Prelude.readFile $ appRoot // \"authentication\"\n let auth = List.head $ lines file\n return auth"), ("webapps/system/ServerInfo","module ServerInfo where\n\nimport Control.Concurrent.MVar\nimport Data.List\nimport Data.Maybe\nimport Hascat.App\nimport Hascat.Config\nimport Hascat.System.Controller\nimport Hascat.Toolkit\nimport Network.HTTP\nimport Network.URI\nimport Text.Html hiding ( start )\nimport qualified Data.ByteString.Lazy as ByteString\nimport qualified Hascat.Protocol as HP\n\n\n\nsystemHandler = SystemHandler respond\n\n\nrespond config stateVar request = do\n let appPath = uriPath (HP.rqURI request)\n case appPath of\n \"\" -> do\n state <- readMVar stateVar\n let title = \"Hascat Server Info\"\n return $ getResponse200 $ thehtml\n << [header\n << [thetitle << title,\n thelink ! [rel \"stylesheet\",\n href \"hascat.css\",\n thetype \"text/css\"]\n << noHtml],\n body << [h1 << title,\n softwareInfoToHtml,\n serverInfoToHtml state]]\n appPath ->\n getFileResponse ((show.getAppContextPath) config // appPath)\n (getAppRoot config // \"static\" // appPath)\n Nothing\n\n\nsoftwareInfoToHtml :: Html\nsoftwareInfoToHtml =\n h2 << \"General Information\" +++\n paragraph << \"Hascat 0.2\"\n\n\nserverInfoToHtml :: State -> Html\nserverInfoToHtml (State processID general _) =\n h2 << \"Server Info\" +++\n tag \"table\" ! [theclass \"b\",\n strAttr \"frame\" \"box\",\n strAttr \"rules\" \"all\"] <<\n [tag \"thead\" << tr <<\n map (th <<) [\"Property\", \"Value\"],\n tag \"tbody\" <<\n map (\\(desc, html) -> tr << [td << desc, td << html])\n [(\"Include paths\",\n concatWithBr $ getIncludePaths (getPluginLoader general)),\n (\"Package configuration files\",\n concatWithBr $ getPkgConfFiles (getPluginLoader general)),\n (\"Process ID\", toHtml $ show processID),\n (\"TCP port\", toHtml $ show (getPort general))]]\n\n\nconcatWithBr :: [String] -> Html\nconcatWithBr items = concatHtml $ intersperse br $ map toHtml items"), ("webapps/system/Deployer","module Deployer where\n\nimport Control.Concurrent.MVar\nimport Control.Monad\nimport Data.List as List\nimport Data.Maybe\nimport Hascat.App\nimport Hascat.Config\nimport Hascat.System.App hiding ( start, stop )\nimport Hascat.System.Controller\nimport Hascat.Toolkit\nimport Network.HTTP\nimport Network.URI\nimport Text.Html hiding ( start ) \nimport Data.ByteString.Lazy\nimport qualified Hascat.Protocol as HP\n\n\nsystemHandler = SystemHandler respond\n\nrespond :: AppConfig -> a -> HP.ServletRequest -> IO (Response ByteString)\nrespond config stateVar request = do\n let headers = HP.rqHeaders request\n authMB = lookupHeader HdrAuthorization headers\n case authMB of\n Nothing -> return (getResponse401 (getAppName config))\n Just auth -> do\n configAuth <- readAuth $ getAppRoot config\n if auth == configAuth then do\n let appPath = uriPath (HP.rqURI request)\n case appPath of\n \"\" ->\n return (getResponse200 (createHtml config))\n appPath ->\n getFileResponse (show (getAppContextPath config) // appPath)\n (getAppRoot config // \"static\" // appPath)\n Nothing\n else return (getResponse401 \"WallyWorld\")\n\n\ncreateHtml :: AppConfig -> Html\ncreateHtml config =\n let title = \"Hascat Application Deployer\"\n in thehtml <<\n [header <<\n [thetitle << title,\n thelink ! [rel \"stylesheet\",\n href \"hascat.css\",\n thetype \"text/css\"] <<\n noHtml],\n body <<\n [h1 << title,\n deploymentHtml config]]\n\n\ndeploymentHtml :: AppConfig -> Html\ndeploymentHtml config =\n thediv ! [theclass \"input\"]\n << form ! [method \"POST\",\n action $ show $ getAppContextPath config,\n enctype \"multipart/form-data\"]\n << [thediv ! [theclass \"formtitle\"] << \"Install New Hascat Application\",\n tag \"table\" ! [strAttr \"frame\" \"void\",\n strAttr \"rules\" \"none\"]\n << tag \"tbody\"\n << [tr << [td << \"Context Path: \",\n td ! [theclass \"padded\"]\n << input ! [thetype \"text\",\n name \"contextPath\",\n size \"40\"]],\n tr << [td << \"Hascat Application Archive: \",\n td ! [theclass \"padded\"]\n << input ! [thetype \"file\",\n name \"app\",\n size \"40\"]]],\n paragraph\n << tag \"button\" ! [thetype \"submit\",\n name \"command\",\n value \"deploy\"]\n << \"Deploy\"]\n\n\nreadAuth :: String -> IO String\nreadAuth appRoot = do\n file <- Prelude.readFile $ appRoot // \"authentication\"\n let auth = List.head $ lines file\n return auth"), ("webapps/cgi/CGI","{-# OPTIONS -XNamedFieldPuns #-}\n\nmodule CGI where\n\nimport Control.OldException\nimport Control.Monad\nimport Data.Maybe\nimport Hascat.App\nimport Hascat.Config\nimport Hascat.Toolkit\nimport Network.HTTP\nimport Network.URI\nimport System.Exit\nimport System.IO\nimport System.Process\nimport Text.Html\nimport Data.List ( elemIndex )\nimport Data.Int\nimport qualified Data.ByteString.Lazy as Lazy hiding ( pack, unpack, span, empty, elemIndex )\nimport qualified Data.ByteString.Lazy.Char8 as Lazy ( pack, unpack, span, empty, elemIndex )\nimport Hascat.Protocol as HP\n\nhandlers = Handlers defaultInit respond defaultDone\n\n\nrespond :: AppConfig -> a -> HP.ServletRequest -> IO (Response Lazy.ByteString)\nrespond config _ (ServletRequest uri method rqHeaders rqInputs rqBody) = do\n let appPath = uriPath uri\n origURL = (show.getAppContextPath) config // appPath\n cgiFile = (getAppRoot config) // appPath\n query = case uriQuery uri of\n \"\" -> []\n '?':query -> [(\"QUERY_STRING\", query)]\n query -> [(\"QUERY_STRING\", query)]\n envs = query\n ++ [(\"REQUEST_METHOD\",show method),\n (\"GATEWAY_INTERFACE\",\"CGI/1.0\")]\n ++ headerToEnvironment rqHeaders\n handle (\\e -> return (getCouldNotStartResponse origURL)) $\n do\n putStrLn (\"Try to start \" ++ cgiFile)\n --print origURL\n --print appPath\n (hStdIn, hStdOut, _, hProcess) <- \n runInteractiveProcess \n cgiFile --Filename of the executable \n [] --Arguments\n Nothing --path to the working directory \n (Just envs) --environment\n when (method == POST) $\n Lazy.hPutStr hStdIn rqBody\n hClose hStdIn \n exitCode <- waitForProcess hProcess\n case exitCode of\n ExitSuccess -> do\n output <- Lazy.hGetContents hStdOut\n let rspCode = (2, 0, 0)\n (rspHeaders, rspBody) = extractHeader output\n return $ Response\n rspCode\n (getReason rspCode)\n rspHeaders \n rspBody\n ExitFailure _ -> return (getCouldNotStartResponse origURL)\n\n\ngetCouldNotStartResponse :: String -> (Response Lazy.ByteString)\ngetCouldNotStartResponse path =\n getErrorResponse (5, 0, 0) [] $\n toHtml (\"Could not start \" ++ path)\n\nextractHeader :: Lazy.ByteString -> ([Header],Lazy.ByteString)\nextractHeader str | Lazy.null str = ([],Lazy.empty)\nextractHeader str | Lazy.take 2 str == Lazy.pack \"\\n\\r\" = ([],str)\nextractHeader str | Lazy.take 1 str == Lazy.pack \"\\n\" = ([],str)\nextractHeader str = \n let (headerStr,remain) = splitAtNewLine str \n in case parseHeader $ Lazy.unpack headerStr of\n Left e -> ([],Lazy.append headerStr remain) --stop at parse error\n Right header -> let (headers,body) = extractHeader remain in\n (header:headers,body)\n\nsplitAtNewLine :: Lazy.ByteString -> (Lazy.ByteString,Lazy.ByteString)\nsplitAtNewLine str = case '\\n' `Lazy.elemIndex` str of\n Nothing -> (str,Lazy.empty)\n Just x -> \n case Lazy.splitAt x str of\n (first,last) | Lazy.take 2 last == Lazy.pack \"\\n\\r\" -> (first,Lazy.drop 2 last)\n (first,last) | Lazy.take 1 last == Lazy.pack \"\\n\" -> (first,Lazy.drop 1 last)\n\nheaderToEnvironment :: [Header] -> [(String,String)]\nheaderToEnvironment headers = \n ( fromMaybeTuple \"CONTENT_LENGTH\" $ lookupHeader HdrContentLength headers)\n ++(fromMaybeTuple \"CONTENT_TYPE\" $ lookupHeader HdrContentType headers)\n ++(fromMaybeTuple \"HTTP_ACCEPT\" $ lookupHeader HdrAccept headers)\n ++(fromMaybeTuple \"HTTP_USER_AGENT\" $ lookupHeader HdrUserAgent headers)\n\nfromMaybeTuple :: a -> Maybe b -> [(a,b)]\nfromMaybeTuple _ Nothing = []\nfromMaybeTuple k (Just x) = [(k,x)]\n"), ("webapps/root/Root","module Root where\n\nimport Data.Maybe\nimport Data.Char\nimport Hascat.App\nimport Hascat.Config\nimport Hascat.Toolkit\nimport Network.HTTP\nimport Network.URI\nimport qualified Data.ByteString.Lazy as ByteString\nimport qualified Hascat.Protocol as HP\n\n\n\nhandlers = Handlers defaultInit respond defaultDone\n\n\nrespond config _ request = do\n let appPath = urlDecode $ uriPath $ HP.rqURI request\n localPath = getAppRoot config // \"static\" // appPath\n showHidden = any (\\x -> map toLower (fst x) == \"showhidden\") (HP.rqInputs request)\n getFileOrDirectoryIndexResponse ((show.getAppContextPath) config // appPath) localPath Nothing showHidden")] staticFiles :: IO [(FilePath,String)] staticFiles = do d <- getCurrentDirectory return $ [("webapps/root/static/index.html","

It Works!

This static file is located at webapps/root/static/index.html

For details check http://www.informatik.uni-kiel.de/~fmi/bachelor.pdf

Example Webapps can be downloaded from http://www.informatik.uni-kiel.de/~fmi/hascat-webapps.tar.gz

\n"),("webapps/system/static/hascat.css","body {\n color: black;\n background-color: white;\n font-family: \"Arial\", \"Helvetica\", sans-serif;\n}\n\n.left {\n text-align: left;\n}\n\n.center {\n text-align: center;\n}\n\n.right {\n text-align: right;\n}\n\n.smaller {\n font-size: smaller;\n}\n\n.nowrap {\n white-space: nowrap;\n}\n\ntable[rules=\"all\"] th, td {\n padding: 4px 6px;\n}\n\ntable.small {\n font-size: small;\n}\n\ntable[frame] thead tr {\n background: #d0e8ff; //#a0d0ff;\n}\n\ntr.noaccess {\n color: silver;\n}\n\nh1 {\n color: black;\n}\n\na {\n color: blue; //#0568ff;\n text-decoration: none;\n}\n\na:hover {\n text-decoration: underline;\n}\n\n.light {\n color: #c0c0c0;\n}\n\n.info {\n background: #e0ff80;\n border: ridge #e0ff80;\n padding: 1ex;\n}\n\n.error {\n background: red;\n border: ridge red;\n padding: 1ex;\n}\n\n\n.rowerror {\n background-color: #ffc0c0;\n font-weight: bold;\n}\n\n.input {\n background-color: #d0e8ff; //#e8e8e8;\n border: 2px outset;\n margin-bottom: 1em;\n padding: 1em;\n width: auto;\n}\n\n.input .formtitle {\n font-weight: bold;\n margin-bottom: 1ex;\n}\n\n.input td.padded {\n padding: 0.5em 1ex;\n}\n\ndiv.group {\n border: 2px groove;\n margin: 1em 0;\n padding: 1em;\n}\n\ncode {\n font-size: 100%;\n}\n\npre {\n font-size: 100%;\n}\n\nimg {\n margin-bottom: 2ex;\n border: none;\n}\n\nimg.left {\n float: left;\n margin-right: 2ex;\n}\n\nimg.right {\n float: right;\n margin-left: 2ex;\n}\n\n#header {\n margin: 0;\n font-size: 16pt;\n}\n@media screen {\n #header {\n background-color: #cc3333;\n font-weight: bold;\n padding: 6px 12px;\n color: white;\n }\n}\n@media print {\n #header {\n display: none;\n }\n}\n\n#path {\n padding: 4px 20px 4px 20px;\n background-color: #999999;\n font-size: 80%;\n color: white;\n}\n\n#path a {\n color: inherit;\n}\n\n#path a:hover {\n color: inherit;\n text-decoration: underline;\n}\n\n#quick {\n padding: 4px 20px 4px 20px;\n background-color: #999999;\n font-size: 80%;\n color: white;\n text-align: right;\n}\n\n#quick a {\n color: inherit;\n}\n\n#quick a:hover {\n color: inherit;\n text-decoration: underline;\n}\n\n#tree {\n width: 20%;\n /* float: left; */\n /* margin-right: 8px; */\n padding: 0;\n background-color: white; /* #e0e0ff; */\n color: black;\n font-size: small;\n}\n\n@media print {\n #tree {\n display: none;\n }\n}\n\n#tree div.self {\n background-color: #eeeeee;\n margin: 4px 0 2px 0;\n padding: 4px 12px;\n font-weight: bold;\n}\n\n#tree div.child {\n /* background-color: #ffffdd; */\n margin: 2px 0;\n padding: 4px 12px;\n}\n\n#tree div.nonchild {\n margin: 4px 0;\n padding: 4px 12px;\n}\n\n#tree div.parent {\n margin: 4px 0;\n padding: 4px 12px;\n font-weight: bold;\n}\n\n#tree a, #tree a:visited, #tree a:hover {\n color: inherit;\n}\n\n#tree a:hover {\n text-decoration: underline;\n}\n\n#tree div.level1 {\n margin-left: 1ex;\n}\n\n#tree div.level2 {\n margin-left: 2ex;\n}\n\n#tree div.level3 {\n margin-left: 3ex;\n}\n\n#tree div.level4 {\n margin-left: 4ex;\n}\n\n#tree div.level5 {\n margin-left: 5ex;\n}\n\n#tree div.level6 {\n margin-left: 6ex;\n}\n\n#content {\n width: 76%;\n /* float: left; */\n margin: 0;\n padding: 20px;\n border-left: thin solid #999999;\n}\n\n#links DIV.box {\n clear: both;\n margin: 2em 0;\n border: thin solid #999999;\n}\n\n#links DIV.box H2 {\n background-color: #999999;\n color: white;\n margin: 0;\n padding: 2px 12px;\n font-family: \"Arial\", \"Helvetica\", sans-serif;\n font-weight: bold;\n font-size: 110%;\n}\n\n#links DIV.box DIV {\n padding: 0.5em 12px;\n}\n\n#brothers {\n padding: 20px;\n border-left: thin solid #999999;\n font-size: small;\n text-align: center;\n}\n\n#footer {\n clear: both;\n margin-top: 4ex;\n border-top: thin solid #999999;\n border-left: thin solid #999999;\n padding: 10px 20px 0 20px;\n font-size: small;\n}\n\n#footer img.w3 {\n float: right;\n width: 88px;\n height: 31px;\n margin-left: 8px;\n}"), ("webapps/system/authentication","Basic ZmxvOm1vaW5zZW4="), ("config.xml","\n\n\n\n \n 8012\n " ++ d ++ "\n \n \n \n \n\n \n \n Root\n Displays Hascat's homepage.\n webapps/root\n Root.o\n /\n 30\n 30\n 30\n \n\n \n Hascat Server Info\n Shows information about Hascat.\n webapps/system\n ServerInfo.o\n /ServerInfo\n 30\n 30\n 30\n \n\n \n Hascat Application Manager\n Manages Hascat applications.\n webapps/system\n Manager.o\n /Manager\n 30\n 30\n 30\n \n\n \n Hascat Application Deployer\n Deploys new Hascat applications.\n webapps/system\n Deployer.o\n /Deployer\n 30\n 30\n 30\n \n\n \n")]