packages feed

hackage-server-0.4: Distribution/Server/Users/Group.hs

{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, ExistentialQuantification #-}
module Distribution.Server.Users.Group (
    UserList(..),
    UserGroup(..),
    GroupDescription(..),
    nullDescription,
    groupName,
    empty,
    add,
    remove,
    member,
    enumerate,
    fromList,
    unions,
    queryGroups
  ) where

import Distribution.Server.Users.Types
import Distribution.Server.Framework.MemSize

import qualified Data.IntSet as IntSet
import Data.Monoid (Monoid)
import Data.SafeCopy (SafeCopy(..), contain)
import Data.Serialize (Serialize)
import qualified Data.Serialize as Serialize
import Data.Typeable (Typeable)
import Control.DeepSeq

import Prelude hiding (id)

-- | Some subset of users, eg those allowed to perform some action.
--
newtype UserList = UserList IntSet.IntSet
  deriving (Eq, Monoid, Serialize, Typeable, Show, MemSize)

instance SafeCopy UserList where
  putCopy = contain . Serialize.put
  getCopy = contain Serialize.get

empty :: UserList
empty = UserList IntSet.empty

add :: UserId -> UserList -> UserList
add (UserId id) (UserList group) = UserList (IntSet.insert id group)

remove :: UserId -> UserList -> UserList
remove (UserId id) (UserList group) = UserList (IntSet.delete id group)

member :: UserId -> UserList -> Bool
member (UserId id) (UserList group) = IntSet.member id group

enumerate :: UserList -> [UserId]
enumerate (UserList group) = map UserId (IntSet.toList group)

fromList :: [UserId] -> UserList
fromList ids = UserList $ IntSet.fromList (map (\(UserId uid) -> uid) ids)

unions :: [UserList] -> UserList
unions groups = UserList (IntSet.unions [ group | UserList group <- groups ])

-- | An abstraction over a UserList for dynamically querying and modifying
-- a user group.
--
-- This structure is not only meant for singleton user groups, but also collections
-- of groups. Some features may provide a UserGroup parametrized by an argument.
--
data UserGroup = UserGroup {
    -- a description of the group for display
    groupDesc :: GroupDescription,
    -- dynamic querying for its members
    queryUserList :: IO UserList,
    -- dynamically add a member (does nothing if already exists)
    -- creates the group if it didn't exist previously
    addUserList :: UserId -> IO (),
    -- dynamically remove a member (does nothing if not present)
    -- creates the group if it didn't exist previously
    removeUserList :: UserId -> IO (),
    -- user groups which can remove from one
    canRemoveGroup :: [UserGroup],
    -- user groups which can add to this one  (use 'fix' to add to self)
    canAddGroup :: [UserGroup]
}

-- | A displayable description for a user group.
--
-- Given a groupTitle of A and a group entity of Nothing, the group will be
-- called "A"; given a groupTitle  of "A" and a groupEntity of Just ("B",
-- Just "C"), the title will be displayed as "A for <a href=C>B</a>".
data GroupDescription = GroupDescription {
    groupTitle :: String,
    groupEntity :: Maybe (String, Maybe String),
    groupPrologue  :: String
}

nullDescription :: GroupDescription
nullDescription = GroupDescription { groupTitle = "", groupEntity = Nothing, groupPrologue = "" }

groupName :: GroupDescription -> String
groupName desc = groupTitle desc ++ maybe "" (\(for, _) -> " for " ++ for) (groupEntity desc)

queryGroups :: [UserGroup] -> IO UserList
queryGroups = fmap unions . mapM queryUserList

-- for use in Caches, really...
instance NFData GroupDescription where
    rnf (GroupDescription a b c) = rnf a `seq` rnf b `seq` rnf c

instance MemSize GroupDescription where
    memSize (GroupDescription a b c) = memSize3 a b c