{-# LANGUAGE CPP #-}
module Hakyll.Preview.Poll
( watchUpdates
) where
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar (newEmptyMVar, takeMVar,
tryPutMVar)
import Control.Exception (AsyncException, fromException,
handle, throw)
import Control.Monad (forever, void, when)
import System.Directory (canonicalizePath)
import System.FilePath (pathSeparators)
import qualified System.FSNotify as FSNotify
#ifdef mingw32_HOST_OS
import Control.Concurrent (threadDelay)
import Control.Exception (IOException, try)
import System.Directory (doesFileExist)
import System.Exit (exitFailure)
import System.FilePath ((</>))
import System.IO (Handle, IOMode (ReadMode),
hClose, openFile)
import System.IO.Error (isPermissionError)
#endif
import Hakyll.Core.Configuration
import Hakyll.Core.Identifier
import Hakyll.Core.Identifier.Pattern
watchUpdates :: Configuration -> IO Pattern -> IO ()
watchUpdates :: Configuration -> IO Pattern -> IO ()
watchUpdates Configuration
conf IO Pattern
update = do
let providerDir :: FilePath
providerDir = Configuration -> FilePath
providerDirectory Configuration
conf
MVar Event
shouldBuild <- forall a. IO (MVar a)
newEmptyMVar
Pattern
pattern <- IO Pattern
update
FilePath
fullProviderDir <- FilePath -> IO FilePath
canonicalizePath FilePath
providerDir
WatchManager
manager <- IO WatchManager
FSNotify.startManager
FilePath -> IO Bool
checkIgnore <- Configuration -> IO (FilePath -> IO Bool)
shouldWatchIgnore Configuration
conf
let allowed :: Event -> IO Bool
allowed Event
event = do
let path :: FilePath
path = Event -> FilePath
FSNotify.eventPath Event
event
relative :: FilePath
relative = forall a. (a -> Bool) -> [a] -> [a]
dropWhile (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` FilePath
pathSeparators) forall a b. (a -> b) -> a -> b
$
forall a. Int -> [a] -> [a]
drop (forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
fullProviderDir) FilePath
path
identifier :: Identifier
identifier = FilePath -> Identifier
fromFilePath FilePath
relative
Bool
shouldIgnore <- FilePath -> IO Bool
checkIgnore FilePath
path
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not Bool
shouldIgnore Bool -> Bool -> Bool
&& Pattern -> Identifier -> Bool
matches Pattern
pattern Identifier
identifier
ThreadId
_ <- IO () -> IO ThreadId
forkIO forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a b. Applicative f => f a -> f b
forever forall a b. (a -> b) -> a -> b
$ do
Event
event <- forall a. MVar a -> IO a
takeMVar MVar Event
shouldBuild
forall e a. Exception e => (e -> IO a) -> IO a -> IO a
handle
(\SomeException
e -> case forall e. Exception e => SomeException -> Maybe e
fromException SomeException
e of
Maybe AsyncException
Nothing -> FilePath -> IO ()
putStrLn (forall a. Show a => a -> FilePath
show SomeException
e)
Just AsyncException
async -> forall a e. Exception e => e -> a
throw (AsyncException
async :: AsyncException))
(forall {p} {p}. p -> p -> IO ()
update' Event
event FilePath
providerDir)
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ WatchManager -> FilePath -> ActionPredicate -> Action -> IO (IO ())
FSNotify.watchTree WatchManager
manager FilePath
providerDir (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. ActionPredicate
isRemove) forall a b. (a -> b) -> a -> b
$ \Event
event -> do
Bool
allowed' <- Event -> IO Bool
allowed Event
event
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
allowed' forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ forall a. MVar a -> a -> IO Bool
tryPutMVar MVar Event
shouldBuild Event
event
where
#ifndef mingw32_HOST_OS
update' :: p -> p -> IO ()
update' p
_ p
_ = forall (f :: * -> *) a. Functor f => f a -> f ()
void IO Pattern
update
#else
update' event provider = do
let path = provider </> FSNotify.eventPath event
fileExists <- doesFileExist path
when fileExists . void $ waitOpen path ReadMode (\_ -> update) 10
waitOpen :: FilePath -> IOMode -> (Handle -> IO r) -> Integer -> IO r
waitOpen _ _ _ 0 = do
putStrLn "[ERROR] Failed to retrieve modified file for regeneration"
exitFailure
waitOpen path mode handler retries = do
res <- try $ openFile path mode :: IO (Either IOException Handle)
case res of
Left ex -> if isPermissionError ex
then do
threadDelay 100000
waitOpen path mode handler (retries - 1)
else throw ex
Right h -> do
handled <- handler h
hClose h
return handled
#endif
isRemove :: FSNotify.Event -> Bool
isRemove :: ActionPredicate
isRemove (FSNotify.Removed {}) = Bool
True
isRemove Event
_ = Bool
False