summaryrefslogtreecommitdiff
path: root/lib/Hakyll/Preview
diff options
context:
space:
mode:
authorJasper Van der Jeugt <m@jaspervdj.be>2017-06-19 11:57:23 +0200
committerJasper Van der Jeugt <m@jaspervdj.be>2017-06-19 11:57:23 +0200
commit67ecff7ad383640bc73d64edc2506c7cc648a134 (patch)
tree6d328e43c3ab86c29a2d775fabaa23618c16fb51 /lib/Hakyll/Preview
parent2df3209bafa08e6b77ee4a8598fc503269513527 (diff)
downloadhakyll-67ecff7ad383640bc73d64edc2506c7cc648a134.tar.gz
Move src/ to lib/, put Init.hs in src/
Diffstat (limited to 'lib/Hakyll/Preview')
-rw-r--r--lib/Hakyll/Preview/Poll.hs119
-rw-r--r--lib/Hakyll/Preview/Server.hs35
2 files changed, 154 insertions, 0 deletions
diff --git a/lib/Hakyll/Preview/Poll.hs b/lib/Hakyll/Preview/Poll.hs
new file mode 100644
index 0000000..e197d3f
--- /dev/null
+++ b/lib/Hakyll/Preview/Poll.hs
@@ -0,0 +1,119 @@
+--------------------------------------------------------------------------------
+{-# 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 System.FSNotify (Event (..), startManager,
+ watchTree)
+
+#ifdef mingw32_HOST_OS
+import Control.Concurrent (threadDelay)
+import Control.Exception (IOException, throw, 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
+
+
+--------------------------------------------------------------------------------
+-- | A thread that watches for updates in a 'providerDirectory' and recompiles
+-- a site as soon as any changes occur
+watchUpdates :: Configuration -> IO Pattern -> IO ()
+watchUpdates conf update = do
+ let providerDir = providerDirectory conf
+ shouldBuild <- newEmptyMVar
+ pattern <- update
+ fullProviderDir <- canonicalizePath $ providerDirectory conf
+ manager <- startManager
+
+ let allowed event = do
+ -- Absolute path of the changed file. This must be inside provider
+ -- dir, since that's the only dir we're watching.
+ let path = eventPath event
+ relative = dropWhile (`elem` pathSeparators) $
+ drop (length fullProviderDir) path
+ identifier = fromFilePath relative
+
+ shouldIgnore <- shouldIgnoreFile conf path
+ return $ not shouldIgnore && matches pattern identifier
+
+ -- This thread continually watches the `shouldBuild` MVar and builds
+ -- whenever a value is present.
+ _ <- forkIO $ forever $ do
+ event <- takeMVar shouldBuild
+ handle
+ (\e -> case fromException e of
+ Nothing -> putStrLn (show e)
+ Just async -> throw (async :: AsyncException))
+ (update' event providerDir)
+
+ -- Send an event whenever something occurs so that the thread described
+ -- above will do a build.
+ void $ watchTree manager providerDir (not . isRemove) $ \event -> do
+ allowed' <- allowed event
+ when allowed' $ void $ tryPutMVar shouldBuild event
+ where
+#ifndef mingw32_HOST_OS
+ update' _ _ = void update
+#else
+ update' event provider = do
+ let path = provider </> eventPath event
+ -- on windows, a 'Modified' event is also sent on file deletion
+ fileExists <- doesFileExist path
+
+ when fileExists . void $ waitOpen path ReadMode (\_ -> update) 10
+
+ -- continuously attempts to open the file in between sleep intervals
+ -- handler is run only once it is able to open the file
+ 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
+
+
+--------------------------------------------------------------------------------
+eventPath :: Event -> FilePath
+eventPath evt = evtPath evt
+ where
+ evtPath (Added p _) = p
+ evtPath (Modified p _) = p
+ evtPath (Removed p _) = p
+
+
+--------------------------------------------------------------------------------
+isRemove :: Event -> Bool
+isRemove (Removed _ _) = True
+isRemove _ = False
diff --git a/lib/Hakyll/Preview/Server.hs b/lib/Hakyll/Preview/Server.hs
new file mode 100644
index 0000000..a84016a
--- /dev/null
+++ b/lib/Hakyll/Preview/Server.hs
@@ -0,0 +1,35 @@
+--------------------------------------------------------------------------------
+-- | Implements a basic static file server for previewing options
+{-# LANGUAGE OverloadedStrings #-}
+module Hakyll.Preview.Server
+ ( staticServer
+ ) where
+
+
+--------------------------------------------------------------------------------
+import Data.String
+import qualified Network.Wai.Handler.Warp as Warp
+import qualified Network.Wai.Application.Static as Static
+import qualified Network.Wai as Wai
+import Network.HTTP.Types.Status (Status)
+
+--------------------------------------------------------------------------------
+import Hakyll.Core.Logger (Logger)
+import qualified Hakyll.Core.Logger as Logger
+
+staticServer :: Logger -- ^ Logger
+ -> FilePath -- ^ Directory to serve
+ -> String -- ^ Host to bind on
+ -> Int -- ^ Port to listen on
+ -> IO () -- ^ Blocks forever
+staticServer logger directory host port = do
+ Logger.header logger $ "Listening on http://" ++ host ++ ":" ++ show port
+ Warp.runSettings warpSettings $
+ Static.staticApp (Static.defaultFileServerSettings directory)
+ where
+ warpSettings = Warp.setLogger noLog
+ $ Warp.setHost (fromString host)
+ $ Warp.setPort port Warp.defaultSettings
+
+noLog :: Wai.Request -> Status -> Maybe Integer -> IO ()
+noLog _ _ _ = return ()