1 module Rakka.SystemConfig
5 , mkSystemConfig -- private
18 import Control.Arrow.ArrowIO
19 import Control.Concurrent.STM
21 import Control.Monad.Trans
22 import qualified Data.ByteString.Char8 as C8
25 import Data.Encoding.UTF8
27 import qualified Data.Map as M
29 import GHC.Conc (unsafeIOToSTM)
31 import qualified Network.HTTP.Lucu.Config as LC
32 import Network.HTTP.Lucu.Utils
36 import Subversion.FileSystem
37 import Subversion.FileSystem.Revision
38 import Subversion.FileSystem.Root
39 import Subversion.Repository
40 import System.FilePath.Posix
41 import System.Log.Logger
43 logger = "Rakka.SystemConfig"
46 data SystemConfig = SystemConfig {
47 scLucuConf :: !LC.Config
48 , scRepository :: !Repository
49 , scCache :: !(TVar (Map FilePath Dynamic))
53 class (Typeable a, Show a) => SysConfValue a where
54 confPath :: a -> FilePath
55 serialize :: a -> String
56 deserialize :: String -> Maybe a
57 defaultValue :: SystemConfig -> a
60 mkSystemConfig :: LC.Config -> Repository -> IO SystemConfig
61 mkSystemConfig lc repos
62 = do cache <- newTVarIO M.empty
63 return $ SystemConfig {
65 , scRepository = repos
69 getSysConf :: forall m a. (MonadIO m, SysConfValue a) => SystemConfig -> m a
73 do let path = confPath (undefined :: a)
75 cache <- readTVar (scCache sc)
77 case M.lookup path cache of
78 Just val -> return $ fromJust $ fromDynamic val
79 Nothing -> do val <- unsafeIOToSTM (getSysConf' sc)
80 writeTVar (scCache sc) (M.insert path (toDyn val) cache)
84 getSysConf' :: forall a. SysConfValue a => SystemConfig -> IO a
86 = do let path = fromConfPath $ confPath (undefined :: a)
88 fs <- getRepositoryFS (scRepository sc)
89 rev <- getYoungestRev fs
90 value <- withRevision fs rev
91 $ do exists <- isFile path
94 -> do str <- getFileContentsLBS path
95 return $ Just $ chomp $ decodeLazy UTF8 str
101 -> case deserialize str of
103 -> do debugM logger ("Got a config value at `" ++ path ++ "': " ++ show val)
106 -> fail ("Got an invalid config value at `" ++ path ++ "': " ++ str)
108 -> do let val = defaultValue sc
109 debugM logger ("Got no config value at `" ++ path ++ "'. Defaulting to " ++ show val)
113 getSysConfA :: (ArrowIO a, SysConfValue c) => SystemConfig -> a b c
114 getSysConfA = arrIO0 . getSysConf
117 fromConfPath :: FilePath -> FilePath
118 fromConfPath = ("/config" </>)
121 serializeStringPairs :: [(String, String)] -> String
122 serializeStringPairs = joinWith "\n" . map serializePair'
124 serializePair' :: (String, String) -> String
125 serializePair' (a, b) = a ++ " " ++ b
128 deserializeStringPairs :: String -> Maybe [(String, String)]
129 deserializeStringPairs = sequence . map deserializePair' . lines
131 deserializePair' :: String -> Maybe (String, String)
132 deserializePair' s = case break (/= ' ') s of
133 (a, ' ':b) -> Just (a, b)
140 newtype SiteName = SiteName String deriving (Show, Typeable)
141 instance SysConfValue SiteName where
142 confPath _ = "siteName"
143 serialize (SiteName name) = name
144 deserialize name = Just (SiteName name)
145 defaultValue _ = SiteName "Rakka"
148 newtype BaseURI = BaseURI URI deriving (Show, Typeable)
149 instance SysConfValue BaseURI where
150 confPath _ = "baseURI"
151 serialize (BaseURI uri) = uriToString id uri ""
152 deserialize uri = fmap BaseURI
153 $ do parsed <- parseURI uri
154 when (uriPath parsed == "" ) (fail undefined)
155 when (last (uriPath parsed) /= '/') (fail undefined)
156 when (uriQuery parsed /= "" ) (fail undefined)
157 when (uriFragment parsed /= "" ) (fail undefined)
160 = let conf = scLucuConf sc
161 host = C8.unpack $ LC.cnfServerHost conf
162 port = case LC.cnfServerPort conf of
163 PortNumber num -> fromIntegral num
166 = "http://" ++ host ++ -- FIXME: consider IPv6 address
169 else ':' : show port) ++ "/"
171 BaseURI $ fromJust $ parseURI defaultURI
174 newtype DefaultPage = DefaultPage String deriving (Show, Typeable)
175 instance SysConfValue DefaultPage where
176 confPath _ = "defaultPage"
177 serialize (DefaultPage name) = name
178 deserialize name = Just (DefaultPage name)
179 defaultValue _ = DefaultPage "MainPage"
182 newtype StyleSheet = StyleSheet String deriving (Show, Typeable)
183 instance SysConfValue StyleSheet where
184 confPath _ = "styleSheet"
185 serialize (StyleSheet name) = name
186 deserialize name = Just (StyleSheet name)
187 defaultValue _ = StyleSheet "StyleSheet/Default"
190 newtype Languages = Languages (Map LanguageTag LanguageName) deriving (Show, Typeable)
191 instance SysConfValue Languages where
192 confPath _ = "languages"
193 serialize (Languages langs) = serializeStringPairs (M.toList langs)
194 deserialize langs = fmap (Languages . M.fromList) (deserializeStringPairs langs)
196 = Languages $ M.fromList [ ("en", "English" )
200 , ("fr", "Français" )
202 , ("gd", "Gàidhlig" )
204 , ("pt", "Português")