hackage-server-0.4: Distribution/Server/Features/Core/State.hs
{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell, BangPatterns #-}
module Distribution.Server.Features.Core.State where
import Distribution.Package
import Distribution.Server.Packages.PackageIndex (PackageIndex)
import qualified Distribution.Server.Packages.PackageIndex as PackageIndex
import Distribution.Server.Packages.Types
import Distribution.Server.Users.Types (UserId)
import Distribution.Server.Framework.MemSize
import Data.Acid (Query, Update, makeAcidic)
import Data.SafeCopy (base, deriveSafeCopy)
import Data.Typeable
import Control.Monad.Reader
import qualified Control.Monad.State as State
import Data.Monoid
import Data.Time (UTCTime)
import Data.List (sortBy)
import Data.Ord (comparing)
import Data.Maybe (maybeToList)
---------------------------------- Index of metadata and tarballs
data PackagesState = PackagesState {
packageList :: !(PackageIndex PkgInfo)
}
deriving (Eq, Typeable, Show)
deriveSafeCopy 0 'base ''PackagesState
instance MemSize PackagesState where
memSize (PackagesState a) = 2 + memSize a
initialPackagesState :: PackagesState
initialPackagesState = PackagesState {
packageList = mempty
}
addPackage :: PackageId -> CabalFileText -> UploadInfo -> Maybe PkgTarball
-> Update PackagesState (Maybe PkgInfo)
addPackage pkgid cabalfile uploadinfo mtarball = do
PackagesState pkgindex <- State.get
case PackageIndex.lookupPackageId pkgindex pkgid of
Just _ -> return Nothing
Nothing -> do
let !pkginfo = PkgInfo {
pkgInfoId = pkgid,
pkgData = cabalfile,
pkgTarball = [ (tarball, uploadinfo)
| tarball <- maybeToList mtarball ],
pkgDataOld = [],
pkgUploadData = uploadinfo
}
pkgindex' = PackageIndex.insert pkginfo pkgindex
State.put $! PackagesState pkgindex'
return (Just pkginfo)
deletePackage :: PackageId -> Update PackagesState (Maybe PkgInfo)
deletePackage pkgid = do
PackagesState pkgindex <- State.get
case PackageIndex.lookupPackageId pkgindex pkgid of
Nothing -> return Nothing
Just pkginfo -> do
let pkgindex' = PackageIndex.deletePackageId pkgid pkgindex
State.put $! PackagesState pkgindex'
return (Just pkginfo)
addPackageRevision :: PackageId -> CabalFileText -> UploadInfo
-> Update PackagesState (Maybe PkgInfo, PkgInfo)
addPackageRevision pkgid cabalfile uploadinfo = do
PackagesState pkgindex <- State.get
case PackageIndex.lookupPackageId pkgindex pkgid of
Just pkginfo -> do
let !pkginfo' = pkginfo {
pkgData = cabalfile,
pkgUploadData = uploadinfo,
pkgDataOld = (pkgData pkginfo, pkgUploadData pkginfo)
: pkgDataOld pkginfo
}
pkgindex' = PackageIndex.insert pkginfo pkgindex
State.put $! PackagesState pkgindex'
return (Just pkginfo, pkginfo')
Nothing -> do
let !pkginfo = PkgInfo {
pkgInfoId = pkgid,
pkgData = cabalfile,
pkgTarball = [],
pkgDataOld = [],
pkgUploadData = uploadinfo
}
pkgindex' = PackageIndex.insert pkginfo pkgindex
State.put $! PackagesState pkgindex'
return (Nothing, pkginfo)
addPackageTarball :: PackageId -> PkgTarball -> UploadInfo
-> Update PackagesState (Maybe (PkgInfo, PkgInfo))
addPackageTarball pkgid tarball uploadinfo =
alterPackage pkgid $ \pkginfo ->
pkginfo {
pkgTarball = (tarball, uploadinfo) : pkgTarball pkginfo
}
setPackageUploader :: PackageId -> UserId
-> Update PackagesState (Maybe (PkgInfo, PkgInfo))
setPackageUploader pkgid uid =
alterPackage pkgid $ \pkginfo ->
pkginfo {
pkgUploadData = (pkgUploadTime pkginfo, uid)
}
setPackageUploadTime :: PackageId -> UTCTime
-> Update PackagesState (Maybe (PkgInfo, PkgInfo))
setPackageUploadTime pkgid time =
alterPackage pkgid $ \pkginfo ->
pkginfo {
pkgUploadData = (time, pkgUploadUser pkginfo)
}
alterPackage :: PackageId -> (PkgInfo -> PkgInfo)
-> Update PackagesState (Maybe (PkgInfo, PkgInfo))
alterPackage pkgid alter = do
PackagesState pkgindex <- State.get
case PackageIndex.lookupPackageId pkgindex pkgid of
Nothing -> return Nothing
Just pkginfo -> do
let !pkginfo' = alter pkginfo
pkgindex' = PackageIndex.insert pkginfo' pkgindex
State.put $! PackagesState pkgindex'
return (Just (pkginfo, pkginfo'))
-- Keep old versions for a bit, for acid-state log compatability
insertPkgIfAbsent :: PkgInfo -> Update PackagesState Bool
insertPkgIfAbsent pkg = do
pkgsState <- State.get
case PackageIndex.lookupPackageId (packageList pkgsState) (packageId pkg) of
Nothing -> do State.put $ pkgsState { packageList = PackageIndex.insert pkg (packageList pkgsState) }
return True
Just{} -> do return False
-- could also return something to indicate existence
mergePkg :: PkgInfo -> Update PackagesState ()
mergePkg pkg = State.modify $ \pkgsState -> pkgsState { packageList = PackageIndex.insertWith mergeFunc pkg (packageList pkgsState) }
where
mergeFunc newPkg oldPkg =
let cabalH : cabalT = sortDesc $ (pkgData newPkg, pkgUploadData newPkg)
: (pkgData oldPkg, pkgUploadData oldPkg)
: pkgDataOld newPkg
++ pkgDataOld oldPkg
tarballs = sortDesc $ pkgTarball newPkg
++ pkgTarball oldPkg
in PkgInfo {
pkgInfoId = pkgInfoId oldPkg -- should equal pkgInfoId newPkg
, pkgData = fst cabalH
, pkgTarball = tarballs
, pkgDataOld = cabalT
, pkgUploadData = snd cabalH
}
sortDesc :: Ord a => [(a1, (a, b))] -> [(a1, (a, b))]
sortDesc = sortBy $ flip (comparing (fst . snd))
deletePackageVersion :: PackageId -> Update PackagesState ()
deletePackageVersion pkg = State.modify $ \pkgsState -> pkgsState { packageList = deleteVersion (packageList pkgsState) }
where deleteVersion = PackageIndex.deletePackageId pkg
replacePackageUploader :: PackageId -> UserId -> Update PackagesState (Either String (PkgInfo, PkgInfo))
replacePackageUploader pkg uid = modifyPkgInfo pkg $ \pkgInfo -> pkgInfo { pkgUploadData = (pkgUploadTime pkgInfo, uid) }
replacePackageUploadTime :: PackageId -> UTCTime -> Update PackagesState (Either String (PkgInfo, PkgInfo))
replacePackageUploadTime pkg time = modifyPkgInfo pkg $ \pkgInfo -> pkgInfo { pkgUploadData = (time, pkgUploadUser pkgInfo) }
addTarball :: PackageId -> PkgTarball -> UploadInfo -> Update PackagesState (Either String (PkgInfo, PkgInfo))
addTarball pkg tarball uploadInfo = modifyPkgInfo pkg $ \pkgInfo -> pkgInfo { pkgTarball = (tarball, uploadInfo) : pkgTarball pkgInfo }
modifyPkgInfo :: PackageId -> (PkgInfo -> PkgInfo) -> Update PackagesState (Either String (PkgInfo, PkgInfo))
modifyPkgInfo pkg f = do
pkgsState <- State.get
case PackageIndex.lookupPackageId (packageList pkgsState) pkg of
Nothing -> return (Left "No such package")
Just pkgInfo -> do
let pkgInfo' = f pkgInfo
State.put $ pkgsState { packageList = PackageIndex.insert pkgInfo' (packageList pkgsState) }
return $ Right (pkgInfo, pkgInfo')
-- |Replace all existing packages and reports
replacePackagesState :: PackagesState -> Update PackagesState ()
replacePackagesState = State.put
getPackagesState :: Query PackagesState PackagesState
getPackagesState = ask
makeAcidic ''PackagesState ['getPackagesState
,'replacePackagesState
,'addPackage
,'deletePackage
,'addPackageRevision
,'addPackageTarball
,'setPackageUploader
,'setPackageUploadTime
-- Keep old versions for a bit,
-- for acid-state log compatability
,'replacePackageUploadTime
,'replacePackageUploader
,'addTarball
,'insertPkgIfAbsent
,'mergePkg
,'deletePackageVersion
]