{-# LANGUAGE NamedFieldPuns #-} {- | This module contains some basis persistence strategies useful for testing, or getting started. -} module Network.Legion.Basics ( newMemoryPersistence, diskPersistence, ) where import Prelude hiding (lookup, readFile, writeFile) import Control.Concurrent.STM (atomically, newTVar, modifyTVar, readTVar, TVar) import Control.Monad.Trans.Class (lift) import Data.Binary (Binary, encode, decode) import Data.Bool (bool) import Data.ByteString (readFile, writeFile) import Data.ByteString.Lazy (toStrict, fromStrict) import Data.Conduit (Source, (=$=), awaitForever, yield) import Data.Conduit.List (sourceList) import Data.Either (rights) import Data.Map (Map, insert, lookup) import Network.Legion.Application (Persistence(Persistence, getState, saveState, list)) import Network.Legion.PartitionKey (PartitionKey, toHex, fromHex) import Network.Legion.PartitionState(PartitionPowerState) import System.Directory (removeFile, doesFileExist, getDirectoryContents) import qualified Data.Map as Map {- | A convenient memory-based persistence layer. Good for testing or for applications (like caches) that don't have durability requirements. -} newMemoryPersistence :: IO (Persistence i s) newMemoryPersistence = do cacheT <- atomically (newTVar Map.empty) return Persistence { getState = fetchState cacheT, saveState = saveState_ cacheT, list = list_ cacheT } where saveState_ :: TVar (Map PartitionKey (PartitionPowerState i s)) -> PartitionKey -> Maybe (PartitionPowerState i s) -> IO () saveState_ cacheT key (Just state) = (atomically . modifyTVar cacheT . insert key) state saveState_ cacheT key Nothing = (atomically . modifyTVar cacheT . Map.delete) key fetchState :: TVar (Map PartitionKey (PartitionPowerState i s)) -> PartitionKey -> IO (Maybe (PartitionPowerState i s)) fetchState cacheT key = atomically $ lookup key <$> readTVar cacheT list_ :: TVar (Map PartitionKey (PartitionPowerState i s)) -> Source IO (PartitionKey, PartitionPowerState i s) list_ cacheT = sourceList . Map.toList =<< lift (atomically (readTVar cacheT)) {- | A convenient way to persist partition states to disk. -} diskPersistence :: (Binary i, Binary s) => FilePath -- ^ The directory under which partition states will be stored. -> Persistence i s diskPersistence directory = Persistence { getState, saveState, list } where getState :: (Binary i, Binary s) => PartitionKey -> IO (Maybe (PartitionPowerState i s)) getState key = let path = toPath key in doesFileExist path >>= bool (return Nothing) ((Just . decode . fromStrict) <$> readFile path) saveState :: (Binary i, Binary s) => PartitionKey -> Maybe (PartitionPowerState i s) -> IO () saveState key (Just state) = writeFile (toPath key) (toStrict (encode state)) saveState key Nothing = let path = toPath key in doesFileExist path >>= bool (return ()) (removeFile path) list :: (Binary i, Binary s) => Source IO (PartitionKey, PartitionPowerState i s) list = do keys <- lift $ readHexList <$> getDirectoryContents directory sourceList keys =$= fillData where fillData = awaitForever (\key -> do let path = toPath key state <- lift ((decode . fromStrict) <$> readFile path) yield (key, state) ) readHexList = rights . fmap fromHex . filter notSys notSys = not . (`elem` [".", ".."]) {- | Convert a key to a path -} toPath :: PartitionKey -> FilePath toPath key = directory ++ "/" ++ toHex key