never executed always true always false
1 module Reanimate.Cache
2 ( cacheFile -- :: FilePath -> (FilePath -> IO ()) -> IO FilePath
3 , cacheMem
4 , cacheDisk
5 , cacheDiskSvg
6 , cacheDiskKey
7 , cacheDiskLines
8 , encodeInt
9 ) where
10
11 import Control.Exception
12 import Control.Monad (unless)
13 import Data.Bits
14 import Data.Hashable
15 import Data.IORef
16 import Data.Map (Map)
17 import qualified Data.Map as Map
18 import Data.Text (Text)
19 import qualified Data.Text as T
20 import qualified Data.Text.IO as T
21 import Graphics.SvgTree (Tree, unparse, pattern None)
22 import Reanimate.Animation (renderTree)
23 import Reanimate.Misc (renameOrCopyFile)
24 import System.Directory
25 import System.FilePath
26 import System.IO
27 import System.IO.Temp
28 import System.IO.Unsafe
29 import Text.XML.Light (Content (..), parseXML)
30
31 -- Memory cache and disk cache
32
33 cacheFile :: FilePath -> (FilePath -> IO ()) -> IO FilePath
34 cacheFile template gen = do
35 root <- getXdgDirectory XdgCache "reanimate"
36 createDirectoryIfMissing True root
37 let path = root </> template
38 hit <- doesFileExist path
39 unless hit $ withSystemTempFile template $ \tmp h -> do
40 hClose h
41 gen tmp
42 renameOrCopyFile tmp path
43 evaluate path
44
45 cacheDisk :: String -> (T.Text -> Maybe a) -> (a -> T.Text) -> (Text -> IO a) -> (Text -> IO a)
46 cacheDisk cacheType parse render gen key = do
47 root <- getXdgDirectory XdgCache "reanimate"
48 createDirectoryIfMissing True root
49 let path = root </> encodeInt (hash key) <.> cacheType
50 hit <- doesFileExist path
51 if hit
52 then do
53 inp <- T.readFile path
54 case parse inp of
55 Nothing -> genCache root path
56 Just val -> pure val
57 else genCache root path
58 where
59 genCache root path = do
60 (tmpPath, tmpHandle) <- openTempFile root (encodeInt (hash key))
61 new <- gen key
62 T.hPutStr tmpHandle (render new)
63 hClose tmpHandle
64 renameOrCopyFile tmpPath path
65 return new
66
67 cacheDiskKey :: Text -> IO Tree -> IO Tree
68 cacheDiskKey key gen = cacheDiskSvg (const gen) key
69
70 cacheDiskSvg :: (Text -> IO Tree) -> (Text -> IO Tree)
71 cacheDiskSvg = cacheDisk "svg" parse render
72 where
73 parse txt = case parseXML txt of
74 [Elem t] -> Just (unparse t)
75 _ -> Nothing
76 render = T.pack . renderTree
77
78 cacheDiskLines :: (Text -> IO [Text]) -> (Text -> IO [Text])
79 cacheDiskLines = cacheDisk "txt" parse render
80 where
81 parse = Just . T.lines
82 render = T.unlines
83
84
85 {-# NOINLINE cache #-}
86 cache :: IORef (Map Text Tree)
87 cache = unsafePerformIO (newIORef Map.empty)
88
89 cacheMem :: (Text -> IO Tree) -> (Text -> IO Tree)
90 cacheMem gen key = do
91 store <- readIORef cache
92 case Map.lookup key store of
93 Just svg -> return svg
94 Nothing -> do
95 svg <- gen key
96 case svg of
97 -- None usually indicates that latex or another tool was misconfigured. In this case,
98 -- don't store the result.
99 None -> pure svg
100 _ -> atomicModifyIORef cache (\m -> (Map.insert key svg m, svg))
101
102 encodeInt :: Int -> String
103 encodeInt i = worker (fromIntegral i) 60
104 where
105 worker :: Word -> Int -> String
106 worker key sh
107 | sh < 0 = []
108 | otherwise =
109 case (key `shiftR` sh) `mod` 64 of
110 idx -> alphabet !! fromIntegral idx : worker key (sh-6)
111 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+$"