module Main where
import Control.Concurrent.STM
import Control.Exception.Extensible
import Control.Monad (when)
import Network.Orchid.Wiki (hWiki, FileStoreType (..))
import Network.Protocol.Uri (jail)
import Network.Salvia.Advanced.ExtendedFileSystem (hExtendedFileSystem)
import Network.Salvia.Handlers.Default (hDefault)
import Network.Salvia.Handlers.Login (readUserDatabase, UserPayload)
import Network.Salvia.Handlers.Session (mkSessions, Sessions)
import Network.Salvia.Httpd (defaultConfig, start, listenAddr, listenPort)
import Network.Socket (PortNumber, inet_addr)
import Paths_orchid_demo
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO
import System.Process.Pipe
-------------------------------------------------------------------------------
main :: IO ()
main = do
argv <- getArgs
prog <- getProgName
conf <- parseOptions prog argv
-- Extract data dir archive.
when (extract conf)
$ extractArchive (extractFrom conf) (extractTo conf)
-- Run web server with wiki.
run
(asServer conf)
(filestore conf)
(bindAddr conf)
(bindPort conf)
(dataDir conf)
(userDB conf)
-------- wiki server ----------------------------------------------------------
stringToFileStore :: String -> Maybe FileStoreType
stringToFileStore kind = lookup kind [("Darcs", Darcs), ("Git", Git)]
run :: Bool -> String -> String -> PortNumber -> FilePath -> FilePath -> IO ()
run serve sfs address port dir users = do
-- Initialize global state.
db <- readUserDatabase users
ioconfig <- defaultConfig
count <- atomically $ newTVar 0
sessions <- mkSessions :: IO (Sessions (UserPayload ()))
addr <- inet_addr address
case stringToFileStore sfs of
Nothing -> putStrLn $ "Error: No such filestore: " ++ sfs
Just fs -> do
-- Alter config and setup handler.
let config = ioconfig { listenAddr = addr, listenPort = port }
let myHandler = if serve
then const $ hExtendedFileSystem dir
else hWiki fs dir dir db
-- Warn about serving user database.
when (maybe False (const True) (jail dir users))
$ putStrLn "Warning: serving user database to the evil outside world."
-- Print status messages and..
putStrLn $ concat ["Listening on ", address, ":", show (listenPort config), "."]
putStrLn $ concat ["Using ", dir, " as wiki repository."]
-- ..off we go!
putStrLn "Server started."
start config $ hDefault count sessions myHandler
extractArchive :: FilePath -> FilePath -> IO ()
extractArchive from to = do
putStrLn $ concat ["Extracting repository from ", from, " to ", to, "."]
s <- pipeString [("unzip", [from, "-d", to])] ""
evaluate $ length s
return ()
-------- command line options parser ------------------------------------------
-- Application configuration type.
data AppConfig =
AppConfig {
extract :: Bool
, extractFrom :: String
, extractTo :: String
, dataDir :: String
, userDB :: String
, asServer :: Bool
, filestore :: String
, bindAddr :: String
, bindPort :: PortNumber
} deriving Show
-- Default application config.
defaultAppConfig :: IO AppConfig
defaultAppConfig = do
dir <- getDataFileName "data.zip"
return $
AppConfig {
extract = False
, extractFrom = dir
, extractTo = "/tmp"
, dataDir = "/tmp/data"
, userDB = "/tmp/data/_user.db"
, asServer = False
, filestore = "Darcs"
, bindAddr = "0.0.0.0"
, bindPort = 8080
}
-- Command line argument declaration.
options :: [OptDescr (AppConfig -> AppConfig)]
options =
let
optExtract = NoArg (\ c -> c { extract = True })
optExtractFrom = OptArg (maybe id (\a c -> c { extractFrom = a })) "<from-cabal>"
optExtractTo = OptArg (maybe id (\a c -> c { extractTo = a })) "/tmp"
optDataDir = OptArg (maybe id (\a c -> c { dataDir = a })) "/tmp/data"
optUserDB = OptArg (maybe id (\a c -> c { userDB = a })) "/tmp/data/_user.db"
optAsServer = NoArg (\ c -> c { asServer = True })
optFileStore = OptArg (maybe id (\a c -> c { filestore = a })) "Darcs"
optBindAddr = OptArg (maybe id (\a c -> c { bindAddr = a })) "0.0.0.0"
optBindPort = OptArg (maybe id (\a c -> c { bindPort = fromIntegral (read a :: Int) })) "8080"
in [
Option [] ["extract"] optExtract "extract a demo repository from archive"
, Option [] ["source-zip"] optExtractFrom "location of repository archive"
, Option [] ["extract-to"] optExtractTo "location to extract demo archive to"
, Option [] ["data-dir"] optDataDir "run demo with this repository"
, Option [] ["user-db"] optUserDB "location of user database file"
, Option [] ["as-server"] optAsServer "do not start wiki but serve directory"
, Option [] ["filestore"] optFileStore "filestore type: Darcs or Git"
, Option [] ["address"] optBindAddr "address to listen on"
, Option [] ["port"] optBindPort "port to bind to"
]
-- Parser for the command line options.
parseOptions :: String -> [String] -> IO AppConfig
parseOptions prog argv = do
def <- defaultAppConfig
case getOpt Permute options argv of
(o, _, []) -> return $ foldl (flip($)) def o
(_, _, errs) -> putStrLn (concat errs ++ usageInfo header options) >> exitFailure
where header = "Usage: " ++ prog ++ " [OPTION...]"