14 import Control.Arrow.ArrowIO
15 import Control.Concurrent.STM
16 import Control.Exception
18 import Control.Monad.Trans
20 import qualified Data.Set as S
22 import Rakka.Storage.DefaultPage
23 import Subversion.Types
24 import System.Directory
25 import System.FilePath
27 import System.Log.Logger
28 import System.Posix.Files
29 import System.Posix.Types
30 import System.Posix.IO
31 import Subversion.FileSystem
32 import Subversion.Repository
33 import Text.HyperEstraier hiding (WriteLock)
35 logger = "Rakka.Storage"
40 stoIndexRevLocked :: !(TVar Bool)
41 , stoIndexRevFile :: !FilePath
42 , stoIndexDB :: !Database
43 , stoRepository :: !Repository
44 , stoMakeDraft :: !(Page -> IO Document)
48 mkStorage :: FilePath -> Repository -> (Page -> IO Document) -> IO Storage
49 mkStorage lsdir repos mkDraft
50 = do let indexDir = lsdir </> "index"
51 revFile = lsdir </> "indexRev"
53 revLocked <- newTVarIO False
54 indexDB <- openIndex indexDir revFile
57 stoIndexRevLocked = revLocked
58 , stoIndexRevFile = revFile
59 , stoIndexDB = indexDB
60 , stoRepository = repos
61 , stoMakeDraft = mkDraft
68 getPage :: MonadIO m => Storage -> PageName -> Maybe RevNum -> m (Maybe Page)
69 getPage = ((liftIO .) .) . getPage' . stoRepository
72 getPage' :: Repository -> PageName -> Maybe RevNum -> IO (Maybe Page)
73 getPage' repos name rev
74 = loadDefaultPage name -- FIXME
77 putPage :: MonadIO m => Storage -> Page -> RevNum -> m ()
78 putPage sto page oldRev
79 = error "FIXME: not implemented"
82 getPageA :: ArrowIO a => Storage -> a (PageName, Maybe RevNum) (Maybe Page)
83 getPageA = arrIO2 . getPage
86 putPageA :: ArrowIO a => Storage -> a (Page, RevNum) ()
87 putPageA = arrIO2 . putPage
90 findAllPages :: Storage -> RevNum -> IO (Set PageName)
91 findAllPages _ 0 = findAllDefaultPages
93 = findAllDefaultPages -- FIXME
96 findChangedPages :: Storage -> RevNum -> RevNum -> IO (Set PageName)
97 findChangedPages sto 0 newRev = findAllPages sto newRev
98 findChangedPages sto oldRev newRev
99 = fail "FIXME: not impl"
102 getCurrentRevNum :: Storage -> IO RevNum
104 = getRepositoryFS (stoRepository sto) >>= getYoungestRev
107 -- casket を R/W モードで開く。成功したらそのまま返し、失敗したら
108 -- indexDir と revFile を削除してから casket を R/W モードで開く。
109 openIndex :: FilePath -> FilePath -> IO Database
110 openIndex indexDir revFile
111 = do ret <- openDatabase indexDir (Writer [])
114 -> do debugM logger ("Opened an H.E. database on " ++ indexDir)
118 -> do warningM logger ("Failed to open an H.E. database on "
119 ++ indexDir ++ ": " ++ show err)
121 indexExists <- doesDirectoryExist indexDir
123 $ removeDirectoryRecursive indexDir
125 revFileExists <- doesFileExist revFile
129 Right db <- openDatabase indexDir (Writer [Create []])
130 noticeM logger ("Created an H.E. database on " ++ indexDir)
135 syncIndex :: Storage -> IO ()
137 = updateIndexRev sto $ \ oldRev ->
138 do debugM logger ("The index revision is currently " ++ show oldRev)
140 newRev <- getCurrentRevNum sto
141 debugM logger ("The repository revision is currently " ++ show newRev)
143 when (newRev /= oldRev) (syncIndex' oldRev newRev)
146 syncIndex' :: RevNum -> RevNum -> IO ()
147 syncIndex' oldRev newRev
148 = do pages <- findChangedPages sto oldRev newRev
149 mapM_ (updateIndex sto newRev) (S.toList pages)
152 updateIndex :: Storage -> RevNum -> PageName -> IO ()
153 updateIndex sto rev name
154 = do pageM <- getPage sto name (Just rev)
158 -> do docIdM <- getDocIdByURI (stoIndexDB sto) (mkRakkaURI name)
161 Just docId -> do removeDocument (stoIndexDB sto) docId [CleaningRemove]
162 infoM logger ("Removed page " ++ name ++ " from the index")
164 -> do draft <- stoMakeDraft sto page
165 putDocument (stoIndexDB sto) draft [CleaningPut]
166 infoM logger ("Indexed page " ++ name ++ " of revision " ++ show (pageRevision page))
169 updateIndexRev :: Storage -> (RevNum -> IO RevNum) -> IO ()
170 updateIndexRev sto f = bracket acquireLock releaseLock update
174 = do atomically $ do revLocked <- readTVar (stoIndexRevLocked sto)
178 writeTVar (stoIndexRevLocked sto) True
179 fd <- openFd (stoIndexRevFile sto) ReadWrite (Just stdFileMode) defaultFileFlags
180 waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
183 releaseLock :: Fd -> IO ()
185 = do setLock fd (Unlock, AbsoluteSeek, 0, 0)
186 atomically $ writeTVar (stoIndexRevLocked sto) False
188 update :: Fd -> IO ()
190 = do fdSeek fd AbsoluteSeek 0
191 size <- return . fromIntegral . fileSize =<< getFdStatus fd
192 (revStr, gotSize) <- fdRead fd size
193 when (size /= gotSize) $ fail ("read " ++ show gotSize ++
194 " bytes but expected " ++ show size ++ " bytes")
196 let rev = case revStr of
202 let revStr' = show rev' ++ "\n"
203 size' = fromIntegral $ length revStr'
205 fdSeek fd AbsoluteSeek 0
207 wroteSize <- fdWrite fd revStr'
208 when (size' /= wroteSize) $ fail ("wrote " ++ show wroteSize ++
209 " bytes but expected " ++ show size' ++ " bytes")