darcsden-1.1: src/DarcsDen/State/Repository.hs
module DarcsDen.State.Repository where
import Control.Monad (forM_, when)
import Control.Monad.Trans
import Data.Char
import Data.Ord
import Data.List
import Data.Maybe (catMaybes, fromJust)
import Data.Time (UTCTime, formatTime)
import Database.CouchDB
import System.Directory
import System.Locale (defaultTimeLocale)
import Text.JSON
import DarcsDen.State.Util
import qualified DarcsDen.Darcs as Darcs
import DarcsDen.Settings
data Repository =
Repository
{ rID :: Maybe Doc
, rRev :: Maybe Rev
, rName :: String
, rOwner :: String
, rDescription :: String
, rWebsite :: String
, rCreated :: UTCTime
, rForkOf :: Maybe Doc
, rMembers :: [String]
, rIsPrivate :: Bool
, rIssueTracker :: Bool
, rIssueCount :: Int
, rBuiltPacks :: Maybe UTCTime
}
deriving (Eq, Show)
instance JSON Repository where
readJSON o = do
id' <- getID o
rev' <- getRev o
name <- getAttr o "name"
owner <- getAttr o "owner"
description <- getAttrOr o "description" ""
website <- getAttrOr o "website" ""
created <- getTime o "created"
forkOf <- getAttrOr o "fork_of" Nothing
members <- getAttrOr o "members" []
isPrivate <- getAttrOr o "is_private" False
issueTracker <- getAttrOr o "issue_tracker" False
issueCount <- getAttrOr o "issue_count" 0
builtPacks <- getMaybeTime o "packs_created"
return Repository
{ rID = Just id'
, rRev = Just rev'
, rName = name
, rOwner = owner
, rDescription = description
, rWebsite = website
, rCreated = created
, rForkOf = forkOf
, rMembers = members
, rIsPrivate = isPrivate
, rIssueTracker = issueTracker
, rIssueCount = issueCount
, rBuiltPacks = builtPacks
}
showJSON r = JSObject . toJSObject $
[ ("name", showJSON (rName r))
, ("owner", showJSON (rOwner r))
, ("description", showJSON (rDescription r))
, ("website", showJSON (rWebsite r))
, ("created", showJSON (formatTime defaultTimeLocale "%F %T" (rCreated r)))
, ("fork_of", showJSON (rForkOf r))
, ("members", showJSON (rMembers r))
, ("is_private", showJSON (rIsPrivate r))
, ("issue_tracker", showJSON (rIssueTracker r))
, ("issue_count", showJSON (rIssueCount r))
, ("packs_created", showJSON (fmap (formatTime defaultTimeLocale "%F %T") (rBuiltPacks r)))
] ++ id' ++ rev'
where
id' =
case rID r of
Just id'' -> [("_id", showJSON (show id''))]
Nothing -> []
rev' =
case rRev r of
Just rev'' -> [("_rev", showJSON (show rev''))]
Nothing -> []
repoOwnerURL :: Repository -> String
repoOwnerURL = (baseUrl ++) . rOwner
-- The named repo's url, no trailing slash.
repoURL :: Repository -> String
repoURL r = baseUrl ++ rOwner r ++ "/" ++ rName r
getRepositoryByID :: MonadIO m => Doc -> m (Maybe Repository)
getRepositoryByID key = do
res <- liftIO $ runDB (getDoc (db "repositories") key)
case res of
Just (_, _, r) -> return (Just r)
Nothing -> return Nothing
getRepository :: MonadIO m => (String, String) -> m (Maybe Repository)
getRepository = getRepositoryByOwnerAndName (doc "repositories")
getOwnerRepository :: MonadIO m => (String, String) -> m (Maybe Repository)
getOwnerRepository = getRepositoryByOwnerAndName (doc "private")
getRepositoryByOwnerAndName :: MonadIO m => Doc -> (String, String) -> m (Maybe Repository)
getRepositoryByOwnerAndName design (un, rn) =
liftIO (runDB query)
where
query = getDocByView
(db "repositories")
design
(doc "by_owner_and_name")
[un, rn]
getRepositoryForks :: MonadIO m => Doc -> m [Repository]
getRepositoryForks key =
liftIO $ fmap (map snd) (runDB query)
where
query = queryView
(db "repositories")
(doc "repositories")
(doc "by_fork")
[("key", showJSON key)]
getRepositories :: MonadIO m => m [Repository]
getRepositories = do
ids <- liftIO $ runDB (getAllDocIds (db "repositories"))
repos <- mapM getRepositoryByID ids
return (filter (not . rIsPrivate) $ catMaybes repos)
getUserRepositories :: MonadIO m => String -> m [Repository]
getUserRepositories = getRepositoriesByOwner (doc "repositories")
getOwnerRepositories :: MonadIO m => String -> m [Repository]
getOwnerRepositories = getRepositoriesByOwner (doc "private")
getRepositoriesByOwner :: MonadIO m => Doc -> String -> m [Repository]
getRepositoriesByOwner design on =
liftIO $
fmap (sortBy (comparing (map toLower . rName)) . map snd)
(runDB query)
where
query = queryView
(db "repositories")
design
(doc "by_owner")
[("key", showJSON on)]
addRepository :: MonadIO m => Repository -> m Repository
addRepository r = do
(id', rev') <- liftIO $ runDB (newDoc (db "repositories") r)
return r { rID = Just id', rRev = Just rev' }
updateRepository :: MonadIO m => Repository -> m (Maybe Repository)
updateRepository r =
case (rID r, rRev r) of
(Just id', Just rev') -> do
update <-
liftIO . runDB $ updateDoc (db "repositories") (id', rev') r
{ rID = Nothing }
case update of
Just (id'', rev'') -> return $ Just r
{ rID = Just id''
, rRev = Just rev''
}
_ -> return Nothing
_ -> return Nothing
deleteRepository :: MonadIO m => Repository -> m Bool
deleteRepository r =
case (rID r, rRev r) of
(Just id', Just rev') -> do
-- orphan a repository's forks
fs <- getRepositoryForks id'
forM_ fs $ \f ->
updateRepository f { rForkOf = Nothing }
liftIO $ runDB (deleteDoc (db "repositories") (id', rev'))
_ -> return False
newRepository :: MonadIO m => Repository -> m Repository
newRepository r = do
new <- addRepository r
Darcs.init repo
return new
where repo = repoDir (rOwner r) (rName r)
destroyRepository :: MonadIO m => Repository -> m Bool
destroyRepository r = do
success <- deleteRepository r
if success
then do
when (deletefiles) $
liftIO . removeDirectoryRecursive $ repoDir (rOwner r) (rName r)
return True
else return False
bootstrapRepository :: MonadIO m => Repository -> String -> m Repository
bootstrapRepository r orig = do
Darcs.getTo orig dest
new <- addRepository r
return new
where
dest = repoDir (rOwner r) (rName r)
forkRepository :: MonadIO m => String -> String -> Repository -> m Repository
forkRepository un rn r = do
dest <- liftIO $ mrn (0-1)
let fork = r {
rID = Nothing
, rRev = Nothing
, rOwner = un
, rName = dest
, rForkOf = rID r
}
bootstrapRepository fork orig
where
orig = repoDir (rOwner r) (rName r)
mrn :: Int -> IO String
mrn i = do
exists <- doesDirectoryExist (repoDir un rn++thenum)
file_exists <- doesFileExist (repoDir un rn++thenum)
if not exists && not file_exists
then return (rn++thenum)
else mrn $ i+1
where thenum = if i == -1 then "" else "_"++show i
moveRepository :: MonadIO m => (String, String) -> Repository -> m ()
moveRepository (o, n) r =
liftIO (renameDirectory (repoDir (rOwner r) (rName r)) (repoDir o n))
renameRepository :: MonadIO m => String -> Repository -> m (Maybe Repository)
renameRepository n r = do
update <- updateRepository (r { rName = n })
case update of
Just _ -> moveRepository (rOwner r, n) r >> return update
_ -> return Nothing
removeMember :: MonadIO m => Repository -> String -> m (Maybe Repository)
removeMember r m =
updateRepository (r { rMembers = filter (/= m) (rMembers r) })
addMember :: MonadIO m => Repository -> String -> m (Maybe Repository)
addMember r m =
updateRepository (r { rMembers = filter (/= m) (rMembers r) ++ [m] })
isMember :: MonadIO m => String -> (String, String) -> m Bool
isMember un key = do
repos <- liftIO $ fmap (map snd) (runDB query)
return (key `elem` repos)
where
query = queryView
(db "repositories")
(doc "private")
(doc "by_member")
[("key", showJSON un)]
withParent :: MonadIO m => (Repository -> m a) -> Repository -> m (Maybe a)
withParent f r = case rForkOf r of
Nothing -> return Nothing
Just p -> getRepositoryByID p >>= f . fromJust >>= return . Just