hakyll/src/Hakyll/Core/Store.hs

156 lines
5.4 KiB
Haskell
Raw Normal View History

2012-10-29 14:01:58 +00:00
--------------------------------------------------------------------------------
-- | A store for storing and retreiving items
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
2010-12-26 15:12:57 +00:00
module Hakyll.Core.Store
( Store
2012-10-29 14:01:58 +00:00
, Result (..)
, new
, set
, get
, delete
2010-12-26 15:12:57 +00:00
) where
2012-10-29 14:01:58 +00:00
--------------------------------------------------------------------------------
import Control.Applicative ((<$>))
import Control.Exception (IOException, handle)
import qualified Crypto.Hash.MD5 as MD5
import Data.Binary (Binary, decodeFile, encodeFile)
import qualified Data.ByteString as B
import qualified Data.Cache.LRU.IO as Lru
import Data.List (intercalate)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Typeable (TypeRep, Typeable, cast, typeOf)
import System.Directory (createDirectoryIfMissing)
import System.Directory (doesFileExist, removeFile)
import System.FilePath ((</>))
import Text.Printf (printf)
2010-12-26 15:12:57 +00:00
2012-10-29 14:01:58 +00:00
--------------------------------------------------------------------------------
-- | Simple wrapper type
data Box = forall a. Typeable a => Box a
2011-05-17 08:57:37 +00:00
2012-10-29 14:01:58 +00:00
--------------------------------------------------------------------------------
2010-12-26 15:12:57 +00:00
data Store = Store
{ -- | All items are stored on the filesystem
storeDirectory :: FilePath
2012-10-29 14:01:58 +00:00
, -- | Optionally, items are also kept in-memory
storeMap :: Maybe (Lru.AtomicLRU FilePath Box)
2010-12-26 15:12:57 +00:00
}
2012-10-29 14:01:58 +00:00
--------------------------------------------------------------------------------
-- | Result of a store query
data Result a
= Found a -- ^ Found, result
| NotFound -- ^ Not found
| WrongType TypeRep TypeRep -- ^ Expected, true type
deriving (Show, Eq)
--------------------------------------------------------------------------------
2010-12-26 15:12:57 +00:00
-- | Initialize the store
2012-10-29 14:01:58 +00:00
new :: Bool -- ^ Use in-memory caching
-> FilePath -- ^ Directory to use for hard disk storage
-> IO Store -- ^ Store
new inMemory directory = do
createDirectoryIfMissing True directory
ref <- if inMemory then Just <$> Lru.newAtomicLRU csize else return Nothing
return Store
{ storeDirectory = directory
2012-10-29 14:01:58 +00:00
, storeMap = ref
}
where
2012-10-29 14:01:58 +00:00
csize = Just 500
--------------------------------------------------------------------------------
-- | Auxiliary: add an item to the in-memory cache
cacheInsert :: Typeable a => Store -> String -> a -> IO ()
cacheInsert (Store _ Nothing) _ _ = return ()
cacheInsert (Store _ (Just lru)) key x =
Lru.insert key (Box x) lru
--------------------------------------------------------------------------------
-- | Auxiliary: get an item from the in-memory cache
cacheLookup :: forall a. Typeable a => Store -> String -> IO (Result a)
cacheLookup (Store _ Nothing) _ = return NotFound
cacheLookup (Store _ (Just lru)) key = do
res <- Lru.lookup key lru
return $ case res of
Nothing -> NotFound
Just (Box x) -> case cast x of
Just x' -> Found x'
Nothing -> WrongType (typeOf (undefined :: a)) (typeOf x)
--------------------------------------------------------------------------------
-- | Auxiliary: delete an item from the in-memory cache
cacheDelete :: Store -> String -> IO ()
cacheDelete (Store _ Nothing) _ = return ()
cacheDelete (Store _ (Just lru)) key = do
_ <- Lru.delete key lru
return ()
2010-12-26 15:12:57 +00:00
2012-10-29 14:01:58 +00:00
--------------------------------------------------------------------------------
2010-12-26 15:12:57 +00:00
-- | Store an item
2012-10-29 14:01:58 +00:00
set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
set store identifier value = do
encodeFile (storeDirectory store </> key) value
cacheInsert store key value
2010-12-26 15:12:57 +00:00
where
2012-10-29 14:01:58 +00:00
key = hash identifier
2010-12-26 15:12:57 +00:00
2012-10-29 14:01:58 +00:00
--------------------------------------------------------------------------------
2010-12-26 15:12:57 +00:00
-- | Load an item
2012-10-29 14:01:58 +00:00
get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
get store identifier = do
-- First check the in-memory map
2012-10-29 14:01:58 +00:00
ref <- cacheLookup store key
case ref of
-- Not found in the map, try the filesystem
2012-05-12 11:17:20 +00:00
NotFound -> do
exists <- doesFileExist path
if not exists
-- Not found in the filesystem either
2011-05-17 08:57:37 +00:00
then return NotFound
-- Found in the filesystem
2012-10-29 14:01:58 +00:00
else do
v <- decodeFile path
cacheInsert store key v
return $ Found v
-- Found in the in-memory map (or wrong type), just return
2012-05-12 11:17:20 +00:00
s -> return s
2010-12-26 15:12:57 +00:00
where
2012-10-29 14:01:58 +00:00
key = hash identifier
path = storeDirectory store </> key
--------------------------------------------------------------------------------
-- | Delete an item
delete :: Store -> [String] -> IO ()
delete store identifier = do
cacheDelete store key
deleteFile $ storeDirectory store </> key
where
key = hash identifier
--------------------------------------------------------------------------------
hash :: [String] -> String
hash = concatMap (printf "%02x") . B.unpack .
MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
--------------------------------------------------------------------------------
-- | Delete a file unless it doesn't exist...
deleteFile :: FilePath -> IO ()
deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile