{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- |AptIO is an instance of the RWS monad used to manage the global -- state and output style parameters of clients of the Apt library, -- such as the autobuilder. module Debian.Repo.IO ( AptIOT -- * AptIO Monad , io , tio , runAptIO , tryAB -- * State , IOState , setRepoMap , getRepoMap , lookupRepository , insertRepository , lookupAptImage , insertAptImage , lookupSourcePackages , insertSourcePackages , lookupBinaryPackages , insertBinaryPackages , readParagraphs , findRelease , putRelease , countTasks ) where import qualified Debian.Control.ByteString as B import Debian.Repo.Types import Control.Exception import Control.Monad.RWS import Control.Monad.Trans import Data.Char import Data.List import qualified Data.Map as Map import Data.Maybe import Debian.URI import Extra.TIO import qualified System.IO as IO import System.Posix.Files import Text.Printf instance Ord FileStatus where compare a b = compare (deviceID a, fileID a, modificationTime a) (deviceID b, fileID b, modificationTime b) instance Eq FileStatus where a == b = compare a b == EQ -- | A new monad to support the IO requirements of the autobuilder. -- This uses the RWS monad. The reader monad is used to store a flag -- indicating whether this is a dry run, and the style information -- associated with each output handle, including indentation, prefixing, -- and replacing the output with one dot per n output characters. -- The state monad stores information used to implement the current -- output style and includes state information about whether the console -- is at the beginning of a line, per-handle state information, and a -- cache of the repositories that have been verified. type AptIOT = RWST () () IOState type AptIO = AptIOT TIO -- | This represents the state of the IO system. The 'bol' flag keeps -- track of whether we are at the beginning of line on the console. -- This is computed in terms of what we have sent to the console, but -- it should be remembered that the order that stdout and stderr are -- sent to the console may not be the same as the order in which they -- show up there. However, this appears to server our purposes for -- now. data IOState = IOState { repoMap :: Map.Map URI (Maybe Repository) -- ^ Map to look up known Repository objects , releaseMap :: Map.Map (URI, ReleaseName) (Maybe Release) -- ^ Map to look up known Release objects , aptImageMap :: Map.Map SliceName (Maybe AptImage) -- ^ Map to look up prepared AptImage objects , sourcePackageMap :: Map.Map FilePath (Maybe (FileStatus, [SourcePackage])) , binaryPackageMap :: Map.Map FilePath (Maybe (FileStatus, [BinaryPackage])) } -- |mark an action that should be run in the regular IO monad io :: CIO m => IO a -> AptIOT m a io = lift . liftIO -- |mark an action that should be run in the terminal IO monad tio :: CIO m => m a -> AptIOT m a tio = lift -- |Perform an AptIO monad task in the IO monad. runAptIO :: CIO m => AptIOT m a -> m a runAptIO action = (runRWST action) () initState >>= \ (a, _, _) -> return a -- |Implementation of try for the AptIO monad. If the task throws -- an exception the initial state will be restored. tryAB :: AptIO a -> AptIO (Either Exception a) tryAB task = do state <- get liftAB (try' state) task where try' state task = do result <- tryTIO task case result of Left e -> return (Left e, state, ()) Right (a, s, _) -> return (Right a, s, ()) liftAB :: (TIO (a, IOState, ()) -> TIO (b, IOState, ())) -> (AptIO a -> AptIO b) liftAB f = {- AptIO . -} mapRWST f -- |The initial output state - at the beginning of the line, no special handle -- state information, no repositories in the repository map. initState :: IOState initState = IOState { repoMap = Map.empty , releaseMap = Map.empty , aptImageMap = Map.empty , sourcePackageMap = Map.empty , binaryPackageMap = Map.empty } setRepoMap :: Map.Map URI (Maybe Repository) -> IOState -> IOState setRepoMap m state = state {repoMap = m} getRepoMap :: IOState -> Map.Map URI (Maybe Repository) getRepoMap state = repoMap state lookupRepository :: URI -> IOState -> Maybe Repository lookupRepository uri state = Map.findWithDefault Nothing uri (repoMap state) insertRepository :: URI -> Repository -> IOState -> IOState insertRepository uri repo state = state {repoMap = Map.insert uri (Just repo) (repoMap state)} lookupAptImage :: SliceName -> IOState -> Maybe AptImage lookupAptImage name state = Map.findWithDefault Nothing name (aptImageMap state) insertAptImage :: SliceName -> AptImage -> IOState -> IOState insertAptImage name image state = state {aptImageMap = Map.insert name (Just image) (aptImageMap state)} lookupSourcePackages :: FilePath -> IOState -> Maybe (FileStatus, [SourcePackage]) lookupSourcePackages key state = Map.findWithDefault Nothing key (sourcePackageMap state) insertSourcePackages :: FilePath -> (FileStatus, [SourcePackage]) -> IOState -> IOState insertSourcePackages key packages state = state {sourcePackageMap = Map.insert key (Just packages) (sourcePackageMap state)} lookupBinaryPackages :: FilePath -> IOState -> Maybe (FileStatus, [BinaryPackage]) lookupBinaryPackages key state = Map.findWithDefault Nothing key (binaryPackageMap state) insertBinaryPackages :: FilePath -> (FileStatus, [BinaryPackage]) -> IOState -> IOState insertBinaryPackages key packages state = state {binaryPackageMap = Map.insert key (Just packages) (binaryPackageMap state)} readParagraphs :: FilePath -> IO [B.Paragraph] readParagraphs path = do --IO.hPutStrLn IO.stderr ("OSImage.paragraphsFromFile " ++ path) -- Debugging output h <- IO.openBinaryFile path IO.ReadMode B.Control paragraphs <- B.parseControlFromHandle path h >>= return . (either (error . show) id) IO.hClose h --IO.hPutStrLn IO.stderr ("OSImage.paragraphsFromFile " ++ path ++ " done.") -- Debugging output return paragraphs findRelease :: Repository -> ReleaseName -> IOState -> Maybe Release findRelease repo dist state = Map.findWithDefault Nothing (repoURI repo, dist) (releaseMap state) putRelease :: Repository -> ReleaseName -> Release -> IOState -> IOState putRelease repo dist release state = state {releaseMap = Map.insert (repoURI repo, dist) (Just release) (releaseMap state)} -- | Perform a list of tasks with log messages. countTasks :: CIO m => [(String, m a)] -> m [a] countTasks tasks = mapM (countTask (length tasks)) (zip [1..] tasks) where countTask :: CIO m => Int -> (Int, (String, m a)) -> m a countTask count (index, (message, task)) = (ePutStrBl (printf "[%2d of %2d] %s:" index count message)) >> {- setStyle (addPrefix " ") -} task