1--------------------------------------------------------------------------------
2-- | A store for storing and retreiving items
3{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE ScopedTypeVariables #-}
5module Hakyll.Core.Store
18--------------------------------------------------------------------------------
19import Control.Applicative ((<$>))
20import Control.Exception (IOException, handle)
21import qualified Crypto.Hash.MD5 as MD5
22import Data.Binary (Binary, decode, encodeFile)
23import qualified Data.ByteString as B
24import qualified Data.ByteString.Lazy as BL
25import qualified Data.Cache.LRU.IO as Lru
26import Data.List (intercalate)
27import Data.Maybe (isJust)
28import qualified Data.Text as T
29import qualified Data.Text.Encoding as T
30import Data.Typeable (TypeRep, Typeable, cast, typeOf)
31import System.Directory (createDirectoryIfMissing)
32import System.Directory (doesFileExist, removeFile)
33import System.FilePath ((</>))
34import System.IO (IOMode (..), hClose, openFile)
35import Text.Printf (printf)
38--------------------------------------------------------------------------------
39-- | Simple wrapper type
40data Box = forall a. Typeable a => Box a
43--------------------------------------------------------------------------------
45 { -- | All items are stored on the filesystem
46 storeDirectory :: FilePath
47 , -- | Optionally, items are also kept in-memory
48 storeMap :: Maybe (Lru.AtomicLRU FilePath Box)
52--------------------------------------------------------------------------------
53-- | Result of a store query
55 = Found a -- ^ Found, result
56 | NotFound -- ^ Not found
57 | WrongType TypeRep TypeRep -- ^ Expected, true type
61--------------------------------------------------------------------------------
62-- | Convert result to 'Maybe'
63toMaybe :: Result a -> Maybe a
64toMaybe (Found x) = Just x
68--------------------------------------------------------------------------------
69-- | Initialize the store
70new :: Bool -- ^ Use in-memory caching
71 -> FilePath -- ^ Directory to use for hard disk storage
72 -> IO Store -- ^ Store
73new inMemory directory = do
74 createDirectoryIfMissing True directory
75 ref <- if inMemory then Just <$> Lru.newAtomicLRU csize else return Nothing
77 { storeDirectory = directory
84--------------------------------------------------------------------------------
85-- | Auxiliary: add an item to the in-memory cache
86cacheInsert :: Typeable a => Store -> String -> a -> IO ()
87cacheInsert (Store _ Nothing) _ _ = return ()
88cacheInsert (Store _ (Just lru)) key x =
89 Lru.insert key (Box x) lru
92--------------------------------------------------------------------------------
93-- | Auxiliary: get an item from the in-memory cache
94cacheLookup :: forall a. Typeable a => Store -> String -> IO (Result a)
95cacheLookup (Store _ Nothing) _ = return NotFound
96cacheLookup (Store _ (Just lru)) key = do
97 res <- Lru.lookup key lru
100 Just (Box x) -> case cast x of
102 Nothing -> WrongType (typeOf (undefined :: a)) (typeOf x)
105--------------------------------------------------------------------------------
106cacheIsMember :: Store -> String -> IO Bool
107cacheIsMember (Store _ Nothing) _ = return False
108cacheIsMember (Store _ (Just lru)) key = isJust <$> Lru.lookup key lru
111--------------------------------------------------------------------------------
112-- | Auxiliary: delete an item from the in-memory cache
113cacheDelete :: Store -> String -> IO ()
114cacheDelete (Store _ Nothing) _ = return ()
115cacheDelete (Store _ (Just lru)) key = do
116 _ <- Lru.delete key lru
120--------------------------------------------------------------------------------
122set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
123set store identifier value = do
124 encodeFile (storeDirectory store </> key) value
125 cacheInsert store key value
127 key = hash identifier
130--------------------------------------------------------------------------------
132get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
133get store identifier = do
134 -- First check the in-memory map
135 ref <- cacheLookup store key
137 -- Not found in the map, try the filesystem
139 exists <- doesFileExist path
141 -- Not found in the filesystem either
143 -- Found in the filesystem
146 cacheInsert store key v
148 -- Found in the in-memory map (or wrong type), just return
151 key = hash identifier
152 path = storeDirectory store </> key
154 -- 'decodeFile' from Data.Binary which closes the file ASAP
156 h <- openFile path ReadMode
157 lbs <- BL.hGetContents h
158 BL.length lbs `seq` hClose h
162--------------------------------------------------------------------------------
164isMember :: Store -> [String] -> IO Bool
165isMember store identifier = do
166 inCache <- cacheIsMember store key
167 if inCache then return True else doesFileExist path
169 key = hash identifier
170 path = storeDirectory store </> key
173--------------------------------------------------------------------------------
175delete :: Store -> [String] -> IO ()
176delete store identifier = do
177 cacheDelete store key
178 deleteFile $ storeDirectory store </> key
180 key = hash identifier
183--------------------------------------------------------------------------------
184-- | Delete a file unless it doesn't exist...
185deleteFile :: FilePath -> IO ()
186deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
189--------------------------------------------------------------------------------
190-- | Mostly meant for internal usage
191hash :: [String] -> String
192hash = concatMap (printf "%02x") . B.unpack .
193 MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list