@@ -3,11 +3,12 @@
3module Network.Wai.Handler.Warp.FileInfoCache (
3module Network.Wai.Handler.Warp.FileInfoCache (
5 FileInfo(..)
5 FileInfo(..)
6− -- * Starter
6+ -- * Functions
7− , fileCacheInit
7+ , withFileInfoCache
10import Control.Exception as E
10import Control.Exception as E
11+import Control.Monad (void)
11import Control.Reaper
12import Control.Reaper
12import Data.ByteString (ByteString)
13import Data.ByteString (ByteString)
13import Data.HashMap.Strict (HashMap)
14import Data.HashMap.Strict (HashMap)
@@ -20,63 +21,75 @@ import Data.Time (UTCTime)
20import Network.Wai.Handler.Warp.Date (uToH)
21import Network.Wai.Handler.Warp.Date (uToH)
24+----------------------------------------------------------------
23data FileInfo = FileInfo {
26data FileInfo = FileInfo {
24 fileInfoName :: !FilePath
27 fileInfoName :: !FilePath
25 , fileInfoSize :: !Integer
28 , fileInfoSize :: !Integer
26− , fileInfoTime :: !HTTPDate -- ^ Modification time
29+ , fileInfoTime :: HTTPDate -- ^ Modification time
27− , fileInfoDate :: !ByteString -- ^ Modification time in the GMT format
30+ , fileInfoDate :: ByteString -- ^ Modification time in the GMT format
28 } deriving (Eq, Show)
31 } deriving (Eq, Show)
30data Entry = Negative | Positive FileInfo
33data Entry = Negative | Positive FileInfo
31type Cache = HashMap FilePath Entry
34type Cache = HashMap FilePath Entry
32−type FileCache = Reaper Cache (FilePath,Entry)
35+type FileInfoCache = Reaper Cache (FilePath,Entry)
34−fileInfo :: FileCache -> FilePath -> IO FileInfo
37+----------------------------------------------------------------
35−fileInfo reaper@Reaper{..} path = do
37− case M.lookup path cache of
38− Just Negative -> throwIO (userError "fileInfo")
39− Just (Positive x) -> return x
40− Nothing -> register reaper path `E.onException` negative reaper path
42−register :: FileCache -> FilePath -> IO FileInfo
39+getInfo :: FilePath -> IO FileInfo
43−register reaper path = do
40+getInfo path = do
44 fs <- getFileStatus path -- file access
41 fs <- getFileStatus path -- file access
45 let regular = not (isDirectory fs)
42 let regular = not (isDirectory fs)
46 readable = fileMode fs `intersectFileModes` ownerReadMode /= 0
43 readable = fileMode fs `intersectFileModes` ownerReadMode /= 0
47− if regular && readable then
44+ if regular && readable then do
48− positive reaper fs path
45+ let time = epochTimeToHTTPDate $ modificationTime fs
46+ date = formatHTTPDate time
47+ size = fromIntegral $ fileSize fs
50+ , fileInfoSize = size
51+ , fileInfoTime = time
52+ , fileInfoDate = date
50− throwIO (userError "go to negative")
56+ throwIO (userError "FileInfoCache:getInfo")
58+----------------------------------------------------------------
60+getAndRegisterInfo :: FileInfoCache -> FilePath -> IO FileInfo
61+getAndRegisterInfo reaper@Reaper{..} path = do
63+ case M.lookup path cache of
64+ Just Negative -> throwIO (userError "FileInfoCache:getAndRegisterInfo")
65+ Just (Positive x) -> return x
66+ Nothing -> positive reaper path `E.onException` negative reaper path
52−positive :: FileCache -> FileStatus -> FilePath -> IO FileInfo
68+positive :: FileInfoCache -> FilePath -> IO FileInfo
53−positive Reaper{..} fs path = do
69+positive Reaper{..} path = do
54− reaperAdd (path,entry)
71+ reaperAdd (path, Positive info)
55 return info
72 return info
59− , fileInfoSize = size fs
60− , fileInfoTime = time
61− , fileInfoDate = formatHTTPDate time
63− size = fromIntegral . fileSize
64− time = epochTimeToHTTPDate (modificationTime fs)
65− entry = Positive info
67−negative :: FileCache -> FilePath -> IO FileInfo
74+negative :: FileInfoCache -> FilePath -> IO FileInfo
68negative Reaper{..} path = do
75negative Reaper{..} path = do
69 reaperAdd (path,Negative)
76 reaperAdd (path,Negative)
70− throwIO (userError "fileInfo")
77+ throwIO (userError "FileInfoCache:negative")
72----------------------------------------------------------------
79----------------------------------------------------------------
74−fileCacheInit :: IO (FilePath -> IO FileInfo)
81+withFileInfoCache :: Int -> ((FilePath -> IO FileInfo) -> IO a) -> IO a
82+withFileInfoCache 0 action = action getInfo
83+withFileInfoCache duration action = E.bracket (initialize duration)
85+ (\c -> action (getAndRegisterInfo c))
87+initialize :: Int -> IO FileInfoCache
75−fileCacheInit = mkReaper settings >>= return . fileInfo
88+initialize duration = mkReaper settings
77 settings = defaultReaperSettings {
90 settings = defaultReaperSettings {
78 reaperAction = override
91 reaperAction = override
79− , reaperDelay = 10000000 -- 10 seconds -- hard-coding: fixme
92+ , reaperDelay = duration * 1000000
80 , reaperCons = uncurry M.insert
93 , reaperCons = uncurry M.insert
81 , reaperNull = M.null
94 , reaperNull = M.null
82 , reaperEmpty = M.empty
95 , reaperEmpty = M.empty
@@ -84,3 +97,6 @@ fileCacheInit = mkReaper settings >>= return . fileInfo
85override :: Cache -> IO (Cache -> Cache)
98override :: Cache -> IO (Cache -> Cache)
86override _ = return $ const M.empty
99override _ = return $ const M.empty
101+terminate :: FileInfoCache -> IO ()
102+terminate x = void $ reaperStop x