module-management-0.20.2: testdata/debian/Debian/Repo/Internal/Repos.hs
-- | Manage state information about the available repositories,
-- releases, OS images, and Apt images.
{-# LANGUAGE FlexibleContexts, FlexibleInstances, ScopedTypeVariables, TemplateHaskell #-}
{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
module Debian.Repo.Internal.Repos
( MonadRepos(getRepos, putRepos)
, ReposState
, runReposT
, putOSImage
, osFromRoot
, AptKey
, getApt
, getAptKey
, putAptImage
, evalMonadApt
, repoByURI
, putRepo
, ReleaseKey
, findRelease
, putRelease
, releaseByKey
, MonadReposCached
, runReposCachedT
, syncOS
) where
import Control.Applicative ((<$>))
import Control.Applicative.Error (maybeRead)
import Control.Exception (SomeException)
import Control.Monad (unless)
import Control.Monad.Catch (bracket, catch, MonadCatch, MonadMask)
import Control.Monad.State (MonadIO(..), MonadState(get, put), StateT(runStateT))
import Control.Monad.Trans (lift)
import Data.Map as Map (empty, fromList, insert, lookup, Map, toList, union)
import Data.Maybe (fromMaybe)
import Debian.Release (ReleaseName)
import Debian.Repo.EnvPath (EnvRoot)
import Debian.Repo.Internal.Apt (AptImage(aptImageRoot))
import Debian.Repo.OSImage (OSImage(osRoot), syncOS')
import Debian.Repo.Prelude.Verbosity (qPutStrLn)
import Debian.Repo.Release (Release(releaseName))
import Debian.Repo.RemoteRepository (RemoteRepository)
import Debian.Repo.Repo (Repo, repoKey, RepoKey(..))
import Debian.Repo.Top (MonadTop, runTopT, sub, TopT)
import Debian.URI (URI')
import System.IO.Error (isDoesNotExistError)
import qualified System.Posix.Files as F (removeLink)
data ReleaseKey = ReleaseKey RepoKey ReleaseName deriving (Eq, Ord, Show)
newtype AptKey = AptKey EnvRoot deriving (Eq, Ord, Show)
-- | This represents the state of the IO system.
data ReposState
= ReposState
{ repoMap :: Map.Map URI' RemoteRepository -- ^ Map to look up known (remote) Repository objects
, releaseMap :: Map.Map ReleaseKey Release -- ^ Map to look up known Release objects
, aptImageMap :: Map.Map AptKey AptImage -- ^ Map to look up prepared AptImage objects
, osImageMap :: Map.Map EnvRoot OSImage -- ^ Map to look up prepared OSImage objects
}
runReposT :: Monad m => StateT ReposState m a -> m a
runReposT action = (runStateT action) initState >>= return . fst
-- |The initial output state - at the beginning of the line, no special handle
-- state information, no repositories in the repository map.
initState :: ReposState
initState = ReposState
{ repoMap = Map.empty
, releaseMap = Map.empty
, aptImageMap = Map.empty
, osImageMap = Map.empty
}
-- | A monad to support the IO requirements of the autobuilder.
class (MonadCatch m, MonadIO m, Functor m) => MonadRepos m where
getRepos :: m ReposState
putRepos :: ReposState -> m ()
instance (MonadCatch m, MonadIO m, Functor m) => MonadRepos (StateT ReposState m) where
getRepos = get
putRepos = put
{-
instance (MonadRepos m, Functor m) => MonadRepos (StateT s m) where
getRepos = lift getRepos
putRepos = lift . putRepos
-}
instance MonadRepos m => MonadRepos (StateT EnvRoot m) where
getRepos = lift getRepos
putRepos = lift . putRepos
modifyRepos :: MonadRepos m => (ReposState -> ReposState) -> m ()
modifyRepos f = getRepos >>= putRepos . f
-- | Like @MonadRepos@, but is also an instance of MonadTop and tries to
-- load and save a list of cached repositories from @top/repoCache@.
class (MonadRepos m, MonadTop m, MonadCatch m) => MonadReposCached m
type ReposCachedT m = TopT (StateT ReposState m)
-- | To run a DebT we bracket an action with commands to load and save
-- the repository list.
runReposCachedT :: (MonadIO m, MonadCatch m, Functor m, MonadMask m) => FilePath -> ReposCachedT m a -> m a
runReposCachedT top action = do
qPutStrLn "Running MonadReposCached..."
r <- runReposT $ runTopT top $ bracket loadRepoCache (\ r -> saveRepoCache >> return r) (\ () -> action)
qPutStrLn "Exited MonadReposCached..."
return r
instance (MonadCatch m, MonadIO m, Functor m) => MonadReposCached (ReposCachedT m)
instance (MonadCatch m, MonadIO m, Functor m) => MonadRepos (ReposCachedT m) where
getRepos = lift get
putRepos = lift . put
putOSImage :: MonadRepos m => OSImage -> m ()
putOSImage repo =
modifyRepos (\ s -> s {osImageMap = Map.insert (osRoot repo) repo (osImageMap s)})
osFromRoot :: MonadRepos m => EnvRoot -> m (Maybe OSImage)
osFromRoot root = Map.lookup root . osImageMap <$> getRepos
putRepo :: MonadRepos m => URI' -> RemoteRepository -> m ()
putRepo uri repo = modifyRepos (\ s -> s {repoMap = Map.insert uri repo (repoMap s)})
repoByURI :: MonadRepos m => URI' -> m (Maybe RemoteRepository)
repoByURI uri = Map.lookup uri . repoMap <$> getRepos
getApt :: MonadRepos m => EnvRoot -> m (Maybe AptImage)
getApt root = (Map.lookup (AptKey root) . aptImageMap) <$> getRepos
getAptKey :: MonadRepos m => EnvRoot -> m (Maybe AptKey)
getAptKey root = fmap (AptKey . aptImageRoot) <$> (getApt root)
findRelease :: (Repo r, MonadRepos m) => r -> ReleaseName -> m (Maybe Release)
findRelease repo dist = (Map.lookup (ReleaseKey (repoKey repo) dist) . releaseMap) <$> getRepos
releaseByKey :: MonadRepos m => ReleaseKey -> m Release
releaseByKey key = do
Just rel <- (Map.lookup key . releaseMap) <$> getRepos
return rel
putRelease :: (Repo r, MonadRepos m) => r -> Release -> m ReleaseKey
putRelease repo release = do
let key = ReleaseKey (repoKey repo) (releaseName release)
modifyRepos (\ s -> s {releaseMap = Map.insert key release (releaseMap s)})
return key
putAptImage :: MonadRepos m => AptImage -> m AptKey
putAptImage repo = do
let key = AptKey (aptImageRoot repo)
modifyRepos (\ s -> s {aptImageMap = Map.insert key repo (aptImageMap s)})
return key
-- | Run MonadOS and update the osImageMap with the modified value
evalMonadApt :: MonadRepos m => StateT AptImage m a -> AptKey -> m a
evalMonadApt task (AptKey key) = do
Just apt <- getApt key
(a, apt') <- runStateT task apt
putAptImage apt'
return a
-- | Load the value of the repo cache map from a file as a substitute for
-- downloading information from the remote repositories. These values may
-- go out of date, as when a new release is added to a repository. When this
-- happens some ugly errors will occur and the cache will have to be flushed.
loadRepoCache :: MonadReposCached m => m ()
loadRepoCache =
do dir <- sub "repoCache"
mp <- liftIO (loadRepoCache' dir `catch` (\ (e :: SomeException) -> qPutStrLn (show e) >> return Map.empty))
modifyRepos (\ s -> s {repoMap = mp})
where
loadRepoCache' :: FilePath -> IO (Map URI' RemoteRepository)
loadRepoCache' repoCache =
do qPutStrLn "Loading repo cache..."
file <- readFile repoCache
case maybeRead file of
Nothing ->
error ("Ignoring invalid repoCache: " ++ show file)
Just pairs ->
qPutStrLn ("Loaded " ++ show (length pairs) ++ " entries from the repo cache.") >>
return (fromList pairs)
-- | Write the repo cache map into a file.
saveRepoCache :: MonadReposCached m => m ()
saveRepoCache =
do path <- sub "repoCache"
live <- repoMap <$> getRepos
repoCache <- liftIO $ loadCache path
let merged = Map.union live repoCache
liftIO (F.removeLink path `catch` (\e -> unless (isDoesNotExistError e) (ioError e)) >>
writeFile path (show . Map.toList $ merged))
return ()
where
-- isRemote uri = uriScheme uri /= "file:"
-- isRemote (uri, _) = uriScheme uri /= "file:"
loadCache :: FilePath -> IO (Map.Map URI' RemoteRepository)
loadCache path =
readFile path `catch` (\ (_ :: SomeException) -> return "[]") >>=
return . Map.fromList . fromMaybe [] . maybeRead
syncOS :: (MonadTop m, MonadRepos m) => OSImage -> EnvRoot -> m OSImage
syncOS srcOS dstRoot = do
dstOS <- liftIO $ syncOS' srcOS dstRoot
putOSImage dstOS
dstOS' <- osFromRoot dstRoot
maybe (error ("syncOS failed for " ++ show dstRoot)) return dstOS'