------------------------------------------------------------------------ -- | -- Module : ALife.Creatur.Counter -- Copyright : (c) Amy de Buitléir 2012-2013 -- License : BSD-style -- Maintainer : amy@nualeargais.ie -- Stability : experimental -- Portability : portable -- -- A simple counter which persists between runs. -- ------------------------------------------------------------------------ module ALife.Creatur.Counter ( Counter(..), PersistentCounter, mkPersistentCounter ) where import ALife.Creatur.Clock (Clock, currentTime, incTime) import ALife.Creatur.Util (modifyLift) import Control.Monad (unless) import Control.Monad.IO.Class (liftIO) import Control.Monad.State (StateT, get, gets, modify) import System.Directory (doesFileExist, createDirectoryIfMissing) import System.FilePath (dropFileName) import System.IO (hGetContents, withFile, Handle, IOMode(ReadMode)) import Text.Read (readEither) class Counter c where current :: StateT c IO Int increment :: StateT c IO () data PersistentCounter = PersistentCounter { cInitialised :: Bool, cValue :: Int, cFilename :: FilePath } deriving (Show, Eq) -- | Creates a counter that will store its value in the specified file. mkPersistentCounter :: FilePath -> PersistentCounter mkPersistentCounter = PersistentCounter False (-1) instance Counter PersistentCounter where current = initIfNeeded >> gets cValue increment = do initIfNeeded modify (\c -> c { cValue=1 + cValue c }) k <- get liftIO $ store k store :: PersistentCounter -> IO () store counter = do let f = cFilename counter createDirectoryIfMissing True $ dropFileName f writeFile f $ show (cValue counter) initIfNeeded :: StateT PersistentCounter IO () initIfNeeded = do isInitialised <- gets cInitialised unless isInitialised $ modifyLift initialise initialise :: PersistentCounter -> IO PersistentCounter initialise counter = do let f = cFilename counter fExists <- doesFileExist f if fExists then do x <- withFile f ReadMode readCounter -- closes file ASAP case x of Left msg -> error $ "Unable to read counter from " ++ f ++ ": " ++ msg Right c -> return $ counter { cInitialised=True, cValue=c } else do let k = counter { cInitialised=True, cValue=0 } return k instance Clock PersistentCounter where currentTime = current incTime = increment readCounter :: Handle -> IO (Either String Int) readCounter h = do s <- hGetContents h let x = readEither s case x of Left msg -> return $ Left (msg ++ "\"" ++ s ++ "\"") Right c -> return $ Right c