, mkStorage -- private
, getPage
- , savePage
+ , putPage
+
+ , getPageA
+ , putPageA
)
where
+import Control.Arrow.ArrowIO
+import Control.Concurrent.STM
+import Control.Exception
+import Control.Monad
+import Control.Monad.Trans
+import Data.Set (Set)
import Rakka.Page
import Rakka.Storage.DefaultPage
+import Subversion.Types
+import System.Directory
+import System.FilePath
+import System.IO
+import System.Log.Logger
+import System.Posix.Files
+import System.Posix.Types
+import System.Posix.IO
+import Subversion.FileSystem
+import Subversion.Repository
+import Text.HyperEstraier hiding (WriteLock)
+
+logger = "Rakka.Storage"
+
+data Storage
+ = Storage {
+ stoIndexRevLocked :: !(TVar Bool)
+ , stoIndexRevFile :: !FilePath
+ , stoIndexDB :: !Database
+ , stoRepository :: !Repository
+ , stoMakeDraft :: !(Page -> IO Document)
+ }
-data Storage = Storage -- FIXME
+mkStorage :: FilePath -> Repository -> (Page -> IO Document) -> IO Storage
+mkStorage lsdir repos mkDraft
+ = do let indexDir = lsdir </> "index"
+ revFile = lsdir </> "indexRev"
+
+ revLocked <- newTVarIO False
+ indexDB <- openIndex indexDir revFile
-mkStorage :: IO Storage -- FIXME
-mkStorage = return Storage
+ let sto = Storage {
+ stoIndexRevLocked = revLocked
+ , stoIndexRevFile = revFile
+ , stoIndexDB = indexDB
+ , stoRepository = repos
+ , stoMakeDraft = mkDraft
+ }
+ syncIndex sto
+ return sto
-getPage :: Storage -> PageName -> IO (Maybe Page)
+
+getPage :: MonadIO m => Storage -> PageName -> m (Maybe Page)
getPage sto name
- = loadDefaultPage name -- FIXME
+ = liftIO $ loadDefaultPage name -- FIXME
-savePage :: Storage -> PageName -> Page -> IO ()
-savePage sto name page
+putPage :: MonadIO m => Storage -> Maybe RevNum -> Page -> m ()
+putPage sto oldRev page
= error "FIXME: not implemented"
+
+
+getPageA :: ArrowIO a => Storage -> a PageName (Maybe Page)
+getPageA = arrIO . getPage
+
+
+putPageA :: ArrowIO a => Storage -> a (Maybe RevNum, Page) ()
+putPageA = arrIO2 . putPage
+
+
+findAllPages :: Storage -> RevNum -> IO (Set PageName)
+findAllPages sto revNum
+ = findAllDefaultPages -- FIXME
+
+
+findChangedPages :: Storage -> RevNum -> RevNum -> IO (Set PageName)
+findChangedPages sto 0 newRev = findAllPages sto newRev
+findChangedPages sto oldRev newRev
+ = fail "fixme: not impl"
+
+
+getCurrentRevNum :: Storage -> IO RevNum
+getCurrentRevNum sto
+ = getRepositoryFS (stoRepository sto) >>= getYoungestRev
+
+
+-- casket を R/W モードで開く。成功したらそのまま返し、失敗したら
+-- indexDir と revFile を削除してから casket を R/W モードで開く。
+openIndex :: FilePath -> FilePath -> IO Database
+openIndex indexDir revFile
+ = do ret <- openDatabase indexDir (Writer [])
+ case ret of
+ Right db
+ -> do debugM logger ("Opened an H.E. database on " ++ indexDir)
+ return db
+
+ Left err
+ -> do warningM logger ("Failed to open an H.E. database on "
+ ++ indexDir ++ ": " ++ show err)
+
+ indexExists <- doesDirectoryExist indexDir
+ when indexExists
+ $ removeDirectoryRecursive indexDir
+
+ revFileExists <- doesFileExist revFile
+ when revFileExists
+ $ removeFile revFile
+
+ Right db <- openDatabase indexDir (Writer [Create []])
+ noticeM logger ("Created an H.E. database on " ++ indexDir)
+
+ return db
+
+
+syncIndex :: Storage -> IO ()
+syncIndex sto
+ = updateIndexRev sto $ \ oldRev ->
+ do debugM logger ("The index revision is currently " ++ show oldRev)
+
+ newRev <- getCurrentRevNum sto
+ debugM logger ("The repository revision is currently " ++ show newRev)
+
+ when (newRev /= oldRev) (syncIndex' oldRev newRev)
+
+ return oldRev -- FIXME
+ --return newRev
+ where
+ syncIndex' :: RevNum -> RevNum -> IO ()
+ syncIndex' oldRev newRev
+ = do pages <- findChangedPages sto oldRev newRev
+ print pages -- FIXME
+
+
+updateIndexRev :: Storage -> (RevNum -> IO RevNum) -> IO ()
+updateIndexRev sto f = bracket acquireLock releaseLock update
+ where
+ acquireLock :: IO Fd
+ acquireLock
+ = do atomically $ do revLocked <- readTVar (stoIndexRevLocked sto)
+ if revLocked then
+ retry
+ else
+ writeTVar (stoIndexRevLocked sto) True
+ fd <- openFd (stoIndexRevFile sto) ReadWrite (Just stdFileMode) defaultFileFlags
+ waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
+ return fd
+
+ releaseLock :: Fd -> IO ()
+ releaseLock fd
+ = do setLock fd (Unlock, AbsoluteSeek, 0, 0)
+ atomically $ writeTVar (stoIndexRevLocked sto) False
+
+ update :: Fd -> IO ()
+ update fd
+ = do fdSeek fd AbsoluteSeek 0
+ size <- return . fromIntegral . fileSize =<< getFdStatus fd
+ (revStr, gotSize) <- fdRead fd size
+ when (size /= gotSize) $ fail ("read " ++ show gotSize ++
+ " bytes but expected " ++ show size ++ " bytes")
+
+ let rev = case revStr of
+ "" -> 0
+ _ -> read revStr
+
+ rev' <- f rev
+
+ let revStr' = show rev' ++ "\n"
+ size' = fromIntegral $ length revStr'
+
+ fdSeek fd AbsoluteSeek 0
+ setFdSize fd 0
+ wroteSize <- fdWrite fd revStr'
+ when (size' /= wroteSize) $ fail ("wrote " ++ show wroteSize ++
+ " bytes but expected " ++ show size' ++ " bytes")
\ No newline at end of file