-- | A store for stroing and retreiving items -- {-# LANGUAGE ExistentialQuantification, ScopedTypeVariables #-} module Hakyll.Core.Store ( Store , StoreGet (..) , makeStore , storeSet , storeGet ) where import Control.Applicative ((<$>)) import System.FilePath (()) import System.Directory (doesFileExist) import Data.Maybe (fromMaybe) import Data.Binary (Binary, encodeFile, decodeFile) import Data.Typeable (Typeable, TypeRep, cast, typeOf) import Hakyll.Core.Identifier import Hakyll.Core.Util.File import qualified Data.Cache.LRU.IO as LRU -- | Items we can store -- data Storable = forall a. (Binary a, Typeable a) => Storable a -- | Result when an item from the store -- data StoreGet a = Found a | NotFound | WrongType TypeRep TypeRep deriving (Show, Eq) -- | Data structure used for the store -- data Store = Store { -- | All items are stored on the filesystem storeDirectory :: FilePath , -- | And some items are also kept in-memory storeLRU :: Maybe (LRU.AtomicLRU FilePath Storable) } -- | The size of the in-memory cache to use in items. storeLRUSize :: Maybe Integer storeLRUSize = Just 500 -- | Initialize the store -- makeStore :: Bool -- ^ Use in-memory caching -> FilePath -- ^ Directory to use for hard disk storage -> IO Store -- ^ Store makeStore inMemory directory = do lru <- if inMemory then Just <$> LRU.newAtomicLRU storeLRUSize else return Nothing return Store { storeDirectory = directory , storeLRU = lru } -- | Auxiliary: add an item to the map -- cacheInsert :: (Binary a, Typeable a) => Store -> FilePath -> a -> IO () cacheInsert (Store _ Nothing) _ _ = return () cacheInsert (Store _ (Just lru)) path value = LRU.insert path (Storable value) lru -- | Auxiliary: get an item from the cache -- cacheLookup :: forall a. (Binary a, Typeable a) => Store -> FilePath -> IO (StoreGet a) cacheLookup (Store _ Nothing) _ = return NotFound cacheLookup (Store _ (Just lru)) path = do res <- LRU.lookup path lru case res of Nothing -> return NotFound Just (Storable s) -> return $ case cast s of Nothing -> WrongType (typeOf s) $ typeOf (undefined :: a) Just s' -> Found s' -- | Create a path -- makePath :: Store -> String -> Identifier a -> FilePath makePath store name identifier = storeDirectory store name group toFilePath identifier "hakyllstore" where group = fromMaybe "" $ identifierGroup identifier -- | Store an item -- storeSet :: (Binary a, Typeable a) => Store -> String -> Identifier a -> a -> IO () storeSet store name identifier value = do makeDirectories path encodeFile path value cacheInsert store path value where path = makePath store name identifier -- | Load an item -- storeGet :: (Binary a, Typeable a) => Store -> String -> Identifier a -> IO (StoreGet a) storeGet store name identifier = do -- First check the in-memory map mv <- cacheLookup store path case mv of -- Not found in the map, try the filesystem NotFound -> do exists <- doesFileExist path if not exists -- Not found in the filesystem either then return NotFound -- Found in the filesystem else do v <- decodeFile path cacheInsert store path v return $ Found v -- Found in the in-memory map, just return s -> return s where path = makePath store name identifier