{-# LANGUAGE CPP #-} module HAppS.State.Saver.Impl.File ( fileReader, fileWriter ) where import HAppS.State.Saver.Types import HAppS.Data.Serialize import Control.Concurrent import Control.Exception ( try ) #if __GLASGOW_HASKELL__ >= 610 import Control.Exception ( IOException ) #endif import qualified Data.ByteString.Lazy.Char8 as L import qualified Data.ByteString.Char8 as B import System.Directory ( createDirectoryIfMissing, renameFile, doesFileExist ) import System.IO import System.Random ( randomIO ) import System.Log.Logger import Text.Printf import Control.Monad import System.FilePath logMF = logM "HAppS.State.Saver.Impl.File" #if __GLASGOW_HASKELL__ < 610 tryE = try #else tryE :: IO a -> IO (Either IOException a) tryE = try #endif formatFilePath :: Int -> String -> FilePath formatFilePath n str = printf "%s-%010d" str n fileReader :: Serialize a => FilePath -> String -> Int -> IO (ReaderStream a) fileReader prefix key cutoff = do let file = prefix formatFilePath cutoff key tryE $ createDirectoryIfMissing True prefix return $ ReaderStream { readerClose = do return () , readerGet = do logMF NOTICE "fileReader: readerGet" allFiles <- getAllFiles prefix key cutoff allData <- mapM B.readFile allFiles return $ (parseAll (L.fromChunks allData), length allFiles) , readerGetUncut = do logMF NOTICE "fileReader: readerGetUncut" allData <- B.readFile file `catch` \_ -> return B.empty return $ parseAll (L.fromChunks [allData]) } parseAll :: Serialize a => L.ByteString -> [a] parseAll = loop where loop l | L.null l = [] loop l = let (a,rest) = deserialize l in a:loop rest fileWriter :: Serialize a => FilePath -> String -> Int -> IO (WriterStream a) fileWriter prefix key cutoffInit = do cutoffVar <- newMVar cutoffInit let getFileName = do cutoff <- readMVar cutoffVar return $ prefix formatFilePath cutoff key file <- getFileName logMF NOTICE ("fileWrter: "++key++" @ "++prefix) tryE $ createDirectoryIfMissing True prefix hmv <- newMVar =<< openBinaryFile file WriteMode return $ WriterStream { writerClose = withMVar hmv hClose , writerAdd = \m f -> do logMF NOTICE "fileWriter: saverAdd" withMVar hmv (\h -> L.hPut h (serialize m) >> hFlush h) forkIO f return () , writerAtomicReplace = \ss -> do h <- takeMVar hmv hClose h file <- getFileName atomicWriteFile file (serialize ss) putMVar hmv =<< openBinaryFile file AppendMode , writerCut = do h <- takeMVar hmv hClose h cutoff <- takeMVar cutoffVar let file = prefix formatFilePath (cutoff+1) key putMVar cutoffVar (cutoff+1) putMVar hmv =<< openBinaryFile file WriteMode return (cutoff+1) } getAllFiles prefix key cutoff = loop cutoff where loop n = do let file = prefix formatFilePath n key exist <- doesFileExist file if exist then liftM (file:) (loop (n+1)) else return [] -- | Just to avoid a dependency. atomicWriteFile path string = do r <- randomIO :: IO Int let p' = path ++ ".atomic-tmp-" ++ show (abs r) L.writeFile p' string renameFile p' path