packages feed

darcsden-1.1: src/DarcsDen/State/Util.hs

module DarcsDen.State.Util where

import Control.Monad
import Control.Monad.Trans
import Data.Time (UTCTime, readTime)
import System.Directory
import System.FilePath
import System.Locale (defaultTimeLocale)
import Database.CouchDB
import Database.Redis.Monad.State (RedisM, runWithRedis)
import Database.Redis.Monad (flushAll)
import Text.JSON
import qualified Database.Redis.Redis as R

import DarcsDen.Settings
import DarcsDen.State.Schema


runDB :: CouchMonad a -> IO a
runDB = runCouchDB couchHost couchPort

withRedis :: (MonadIO m) => RedisM a -> m a
withRedis a = liftIO $ do
    c <- R.connect redisHost redisPort
    runWithRedis c a

getDocByView :: (JSON a, JSON b) => DB -> Doc -> Doc -> a -> CouchMonad (Maybe b)
getDocByView db' design view key = do
    get <- queryView db' design view [("key", showJSON key)]
    case get of
         [] -> return Nothing
         ((_, val):_) -> return (Just val)

-- . is allowed in repo names except at the beginning,
-- for this to be safe / and \ must remain disallowed.
charIsSane :: Char -> Bool
charIsSane = flip elem (['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "-_.")

isSane :: String -> Bool
isSane ('.':_) = False
isSane s       = all charIsSane s

saneName :: String -> String
saneName = filter charIsSane

userDir :: String -> FilePath
userDir un = usersDir </> saneName un

repoDir :: String -> String -> FilePath
repoDir un rn = userDir un </> saneName rn

getAttr :: JSON a => JSValue -> String -> Result a
getAttr (JSObject o) n =
    maybe
        (fail ("object missing `" ++ n ++ "': " ++ show o))
        readJSON
        (lookup n (fromJSObject o))
getAttr js n =
    fail ("not an object (needed `" ++ n ++ "' attribute): " ++ show js)

getAttrOr :: JSON a => JSValue -> String -> a -> Result a
getAttrOr (JSObject o) n d =
    maybe (return d) readJSON (lookup n (fromJSObject o))
getAttrOr js n _ =
    fail ("not an object (needed `" ++ n ++ "' attribute): " ++ show js)

getOneOf :: JSON a => JSValue -> String -> String -> Result a
getOneOf (JSObject o) a b =
    maybe (getAttr (JSObject o) b) readJSON (lookup a (fromJSObject o))
getOneOf js n _ =
    fail ("not an object (needed `" ++ n ++ "' attribute): " ++ show js)

getTime :: JSValue -> String -> Result UTCTime
getTime o n = fmap (readTime defaultTimeLocale "%F %T") (getAttr o n)

getMaybeTime :: JSValue -> String -> Result (Maybe UTCTime)
getMaybeTime o n =
   (getAttrOr o n Nothing :: Result (Maybe String)) >>=
   ((return.fmap (readTime defaultTimeLocale "%F %T")) :: Maybe String -> Result (Maybe UTCTime))

getID :: JSValue -> Result Doc
getID o = getAttr o "_id"

getRev :: JSValue -> Result Rev
getRev o = fmap rev (getAttr o "_rev")

resetAllState :: MonadIO m => m ()
resetAllState = do
    withRedis flushAll
    liftIO $ do
        runDB $ do
	    mapM_ dropDB ["repositories", "users", "issues", "comments"]
            createFreshState False
	doesDirectoryExist usersDir >>= flip when (removeDirectoryRecursive usersDir)

createFreshState :: Bool -> CouchMonad ()
createFreshState verbose = do
    when verbose $ liftIO (putStrLn "creating databases...")
    createDB "repositories"
    createDB "users"
    createDB "issues"
    createDB "comments"

    when verbose $ liftIO (putStrLn "creating repository design documents...")
    forM_ repoDesigns $ \js ->
        newDoc (db "repositories") js

    when verbose $ liftIO (putStrLn "creating user design documents...")
    forM_ userDesigns $ \js ->
        newDoc (db "users") js

    when verbose $ liftIO (putStrLn "creating issue design documents...")
    forM_ issueDesigns $ \js ->
        newDoc (db "issues") js

    when verbose $ liftIO (putStrLn "creating comment design documents...")
    forM_ commentDesigns $ \js ->
        newDoc (db "comments") js

    when verbose $ liftIO (putStrLn "All set!")