-- -- Copyright © 2013-2014 Anchor Systems, Pty Ltd and Others -- -- The code in this file, and the program it is a part of, is -- made available to you by its authors as open source software: -- you can redistribute it and/or modify it under the terms of -- the 3-clause BSD licence. -- {-# OPTIONS_GHC -fno-warn-orphans #-} module Marquise.IO.SpoolFile ( newRandomPointsSpoolFile , newRandomContentsSpoolFile ) where import Control.Applicative import Control.Concurrent (threadDelay) import Control.Monad.State import Data.ByteString (ByteString) import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as LB import Data.Maybe import Marquise.Classes import Marquise.IO.FFI import Marquise.Types import System.Directory import System.FilePath.Posix import System.IO import System.IO.Unsafe import System.Posix.Files import System.Posix.IO (closeFd) import System.Posix.Temp import System.Posix.Types (Fd) instance MarquiseSpoolFileMonad IO where randomSpoolFiles sn = SpoolFiles <$> newRandomPointsSpoolFile sn <*> newRandomContentsSpoolFile sn createDirectories sn = mapM_ (createDirectoryIfMissing True . ($sn)) [ newPointsDir , newContentsDir , curPointsDir , curContentsDir] appendPoints spools = doAppend (pointsSpoolFile spools) appendContents spools = doAppend (contentsSpoolFile spools) nextPoints sn = nextSpoolContents (newPointsDir sn) (curPointsDir sn) nextContents sn = nextSpoolContents (newContentsDir sn) (curContentsDir sn) close _ = c_sync newRandomSpoolFile :: FilePath -> IO FilePath newRandomSpoolFile path = do (spool_file, handle) <- mkstemp path hClose handle return spool_file -- | Creates and returns a new points spool file from a namespace newRandomPointsSpoolFile :: SpoolName -> IO FilePath newRandomPointsSpoolFile = newRandomSpoolFile . newPointsDir -- | Creates and returns a new contents spool file from a namespace newRandomContentsSpoolFile :: SpoolName -> IO FilePath newRandomContentsSpoolFile = newRandomSpoolFile . newContentsDir -- | Grab the next avaliable spool file, providing that file as a lazy -- bytestring and an action to close it, wiping the file. nextSpoolContents :: FilePath -> FilePath -> IO (Maybe (L.ByteString, IO ())) nextSpoolContents new_dir cur_dir = do -- First check for any work already in the work spool dir. work <- tryCurDir cur_dir case work of Nothing -> -- No existing work, get some new work out of the spool -- directory then. rotate new_dir cur_dir >> return Nothing Just (fp, lock_fd) -> do threadDelay 100000 -- Ensure that any slow writes are done contents <- LB.readFile fp let close_f = removeLink fp >> closeFd lock_fd return $ Just (contents, close_f) -- | Check the work directory for any outstanding work, if there is a potential -- candidate, lock it. If that fails, try the next. tryCurDir :: FilePath -> IO (Maybe (FilePath, Fd)) tryCurDir cur_dir = listToMaybe . catMaybes <$> (getAbsoluteDirectoryFiles cur_dir >>= mapM lazyLock) where lazyLock :: FilePath -> IO (Maybe (FilePath, Fd)) lazyLock fp = unsafeInterleaveIO $ do lock <- tryLock fp case lock of Nothing -> return Nothing Just lock_fd -> return . Just $ (fp, lock_fd) -- Attempt to rotate all files from src folder to dst folder. rotate :: FilePath -> FilePath -> IO () rotate src dst = do candidates <- getAbsoluteDirectoryFiles src unless (null candidates) (mapM_ doMove candidates) where doMove src_file = do (new_path, h) <- mkstemp dst hClose h renameFile src_file new_path getAbsoluteDirectoryFiles :: FilePath -> IO [FilePath] getAbsoluteDirectoryFiles = getAbsoluteDirectoryContents >=> filterM doesFileExist getAbsoluteDirectoryContents :: FilePath -> IO [FilePath] getAbsoluteDirectoryContents fp = map (\rel -> joinPath [fp, rel]) <$> getDirectoryContents fp doAppend :: FilePath -> ByteString -> IO () doAppend = S.appendFile newPointsDir :: SpoolName -> FilePath newPointsDir = specificSpoolDir ["points", "new"] newContentsDir :: SpoolName -> FilePath newContentsDir = specificSpoolDir ["contents", "new"] curPointsDir :: SpoolName -> FilePath curPointsDir = specificSpoolDir ["points", "cur"] curContentsDir :: SpoolName -> FilePath curContentsDir = specificSpoolDir ["contents", "cur"] specificSpoolDir :: [String] -> SpoolName -> FilePath specificSpoolDir subdirs sn = addTrailingPathSeparator (joinPath (baseSpoolDir sn:subdirs)) baseSpoolDir :: SpoolName -> FilePath baseSpoolDir (SpoolName sn) = joinPath ["/var/spool/marquise/", sn]