1 module Rakka.Storage.Impl
7 import Control.Concurrent
8 import Control.Concurrent.STM
9 import Control.Exception
13 import qualified Data.Set as S
16 import Rakka.Storage.DefaultPage
17 import Rakka.Storage.Repos
18 import Rakka.Storage.Types
19 import Subversion.Types
20 import Subversion.FileSystem
21 import Subversion.Repository
22 import System.Directory
23 import System.FilePath
25 import System.Log.Logger
26 import System.Posix.Files
27 import System.Posix.Types
28 import System.Posix.IO
29 import Text.HyperEstraier hiding (WriteLock)
33 logger = "Rakka.Storage"
36 getPage' :: Repository -> PageName -> Maybe RevNum -> IO (Maybe Page)
37 getPage' repos name rev
38 = do page <- loadPageInRepository repos name rev
40 Nothing -> loadDefaultPage name
44 findAllPages :: Repository -> RevNum -> IO (Set PageName)
45 findAllPages _ 0 = findAllDefaultPages
46 findAllPages repos rev = do reposPages <- findAllPagesInRevision repos rev
47 defaultPages <- findAllDefaultPages
48 return (reposPages `S.union` defaultPages)
51 findChangedPages :: Repository -> RevNum -> RevNum -> IO (Set PageName)
52 findChangedPages repos 0 newRev = findAllPages repos newRev
53 findChangedPages repos oldRev newRev
54 = mapM (findChangedPagesAtRevision repos) [oldRev + 1 .. newRev]
59 getCurrentRevNum :: Repository -> IO RevNum
60 getCurrentRevNum repos
61 = getRepositoryFS repos >>= getYoungestRev
64 startIndexManager :: FilePath -> Repository -> (Page -> IO Document) -> IO (TChan IndexReq)
65 startIndexManager lsdir repos mkDraft
66 = do chan <- newTChanIO
67 index <- openIndex indexDir revFile
68 forkIO (loop chan index)
71 indexDir = lsdir </> "index"
72 revFile = lsdir </> "indexRev"
74 loop :: TChan IndexReq -> Database -> IO ()
76 = do req <- atomically $ readTChan chan
79 -> do noticeM logger "Rebuilding the H.E. index..."
81 removeDirectoryRecursive indexDir
82 index' <- openIndex indexDir revFile
83 syncIndex' index' revFile repos mkDraft
87 -> do syncIndex' index revFile repos mkDraft
91 -> do result <- searchIndex index cond
92 atomically $ putTMVar var result
96 -- casket を R/W モードで開く。成功したらそのまま返し、失敗したら
97 -- indexDir と revFile を削除してから casket を R/W モードで開く。
98 openIndex :: FilePath -> FilePath -> IO Database
99 openIndex indexDir revFile
100 = do ret <- openDatabase indexDir (Writer [])
103 -> do debugM logger ("Opened an H.E. index on " ++ indexDir)
107 -> do noticeM logger ("Failed to open an H.E. index on "
108 ++ indexDir ++ ": " ++ show err)
110 indexExists <- doesDirectoryExist indexDir
112 $ removeDirectoryRecursive indexDir
114 revFileExists <- doesFileExist revFile
118 Right index <- openDatabase indexDir (Writer [Create []])
119 addAttrIndex index "@uri" SeqIndex
120 addAttrIndex index "rakka:revision" SeqIndex
121 noticeM logger ("Created an H.E. index on " ++ indexDir)
126 syncIndex' :: Database -> FilePath -> Repository -> (Page -> IO Document) -> IO ()
127 syncIndex' index revFile repos mkDraft
128 = updateIndexRev revFile $ \ oldRev ->
129 do debugM logger ("The index revision is currently " ++ show oldRev)
131 newRev <- getCurrentRevNum repos
132 debugM logger ("The repository revision is currently " ++ show newRev)
134 when (oldRev == 0 || newRev /= oldRev)
135 $ syncIndex'' oldRev newRev
138 syncIndex'' :: RevNum -> RevNum -> IO ()
139 syncIndex'' oldRev newRev
140 = do pages <- findChangedPages repos oldRev newRev
141 mapM_ (updateIndex index repos mkDraft newRev) (S.toList pages)
144 searchIndex :: Database -> Condition -> IO [(PageName, RevNum)]
145 searchIndex index cond
146 = searchDatabase index cond >>= mapM fromId
148 fromId :: DocumentID -> IO (PageName, RevNum)
150 = do uri <- getDocURI index docId
151 rev <- getDocAttr index docId "rakka:revision"
152 >>= return . read . fromJust
153 return (decodePageName $ uriPath uri, rev)
156 updateIndex :: Database
158 -> (Page -> IO Document)
162 updateIndex index repos mkDraft rev name
163 = do pageM <- getPage' repos name (Just rev)
167 -> do docIdM <- getDocIdByURI index (mkRakkaURI name)
170 Just docId -> do removeDocument index docId [CleaningRemove]
171 infoM logger ("Removed page " ++ name ++ " from the index")
173 -> do draft <- mkDraft page
174 putDocument index draft [CleaningPut]
175 infoM logger ("Indexed page " ++ name ++ " of revision " ++ show (pageRevision page))
178 updateIndexRev :: FilePath -> (RevNum -> IO RevNum) -> IO ()
179 updateIndexRev revFile f = bracket acquireLock releaseLock update
183 = do fd <- openFd revFile ReadWrite (Just stdFileMode) defaultFileFlags
184 waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
187 releaseLock :: Fd -> IO ()
189 = setLock fd (Unlock, AbsoluteSeek, 0, 0)
191 update :: Fd -> IO ()
193 = do fdSeek fd AbsoluteSeek 0
194 size <- return . fromIntegral . fileSize =<< getFdStatus fd
195 (revStr, gotSize) <- fdRead fd size
196 when (size /= gotSize) $ fail ("read " ++ show gotSize ++
197 " bytes but expected " ++ show size ++ " bytes")
199 let rev = case revStr of
205 let revStr' = show rev' ++ "\n"
206 size' = fromIntegral $ length revStr'
208 fdSeek fd AbsoluteSeek 0
210 wroteSize <- fdWrite fd revStr'
211 when (size' /= wroteSize) $ fail ("wrote " ++ show wroteSize ++
212 " bytes but expected " ++ show size' ++ " bytes")