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--------------------------------------------------------------------------------
53instance Show Store where
57--------------------------------------------------------------------------------
58-- | Result of a store query
60 = Found a -- ^ Found, result
61 | NotFound -- ^ Not found
62 | WrongType TypeRep TypeRep -- ^ Expected, true type
66--------------------------------------------------------------------------------
67-- | Convert result to 'Maybe'
68toMaybe :: Result a -> Maybe a
69toMaybe (Found x) = Just x
73--------------------------------------------------------------------------------
74-- | Initialize the store
75new :: Bool -- ^ Use in-memory caching
76 -> FilePath -- ^ Directory to use for hard disk storage
77 -> IO Store -- ^ Store
78new inMemory directory = do
79 createDirectoryIfMissing True directory
80 ref <- if inMemory then Just <$> Lru.newAtomicLRU csize else return Nothing
82 { storeDirectory = directory
89--------------------------------------------------------------------------------
90-- | Auxiliary: add an item to the in-memory cache
91cacheInsert :: Typeable a => Store -> String -> a -> IO ()
92cacheInsert (Store _ Nothing) _ _ = return ()
93cacheInsert (Store _ (Just lru)) key x =
94 Lru.insert key (Box x) lru
97--------------------------------------------------------------------------------
98-- | Auxiliary: get an item from the in-memory cache
99cacheLookup :: forall a. Typeable a => Store -> String -> IO (Result a)
100cacheLookup (Store _ Nothing) _ = return NotFound
101cacheLookup (Store _ (Just lru)) key = do
102 res <- Lru.lookup key lru
105 Just (Box x) -> case cast x of
107 Nothing -> WrongType (typeOf (undefined :: a)) (typeOf x)
110--------------------------------------------------------------------------------
111cacheIsMember :: Store -> String -> IO Bool
112cacheIsMember (Store _ Nothing) _ = return False
113cacheIsMember (Store _ (Just lru)) key = isJust <$> Lru.lookup key lru
116--------------------------------------------------------------------------------
117-- | Auxiliary: delete an item from the in-memory cache
118cacheDelete :: Store -> String -> IO ()
119cacheDelete (Store _ Nothing) _ = return ()
120cacheDelete (Store _ (Just lru)) key = do
121 _ <- Lru.delete key lru
125--------------------------------------------------------------------------------
127set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
128set store identifier value = do
129 encodeFile (storeDirectory store </> key) value
130 cacheInsert store key value
132 key = hash identifier
135--------------------------------------------------------------------------------
137get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
138get store identifier = do
139 -- First check the in-memory map
140 ref <- cacheLookup store key
142 -- Not found in the map, try the filesystem
144 exists <- doesFileExist path
146 -- Not found in the filesystem either
148 -- Found in the filesystem
151 cacheInsert store key v
153 -- Found in the in-memory map (or wrong type), just return
156 key = hash identifier
157 path = storeDirectory store </> key
159 -- 'decodeFile' from Data.Binary which closes the file ASAP
161 h <- openFile path ReadMode
162 lbs <- BL.hGetContents h
163 BL.length lbs `seq` hClose h
167--------------------------------------------------------------------------------
169isMember :: Store -> [String] -> IO Bool
170isMember store identifier = do
171 inCache <- cacheIsMember store key
172 if inCache then return True else doesFileExist path
174 key = hash identifier
175 path = storeDirectory store </> key
178--------------------------------------------------------------------------------
180delete :: Store -> [String] -> IO ()
181delete store identifier = do
182 cacheDelete store key
183 deleteFile $ storeDirectory store </> key
185 key = hash identifier
188--------------------------------------------------------------------------------
189-- | Delete a file unless it doesn't exist...
190deleteFile :: FilePath -> IO ()
191deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
194--------------------------------------------------------------------------------
195-- | Mostly meant for internal usage
196hash :: [String] -> String
197hash = concatMap (printf "%02x") . B.unpack .
198 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