ema-0.1.0.0: src/Ema/Helper/FileSystem.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
-- | Helper to read a directory of files, and observe it for changes.
--
-- Use @new@ in conjunction with @observe@ in your @runEma@ function call.
module Ema.Helper.FileSystem
( filesMatching,
onChange,
FileAction (..),
)
where
import Control.Concurrent (threadDelay)
import Control.Exception (finally)
import Control.Monad.Logger
import System.Directory (canonicalizePath)
import System.FSNotify
( ActionPredicate,
Event (..),
StopListening,
WatchManager,
watchTree,
withManager,
)
import System.FilePath (makeRelative)
import System.FilePattern (FilePattern)
import System.FilePattern.Directory (getDirectoryFiles)
import UnliftIO (MonadUnliftIO, withRunInIO)
type FolderPath = FilePath
log :: MonadLogger m => LogLevel -> Text -> m ()
log = logWithoutLoc "Helper.FileSystem"
filesMatching :: (MonadIO m, MonadLogger m) => FolderPath -> [FilePattern] -> m [FilePath]
filesMatching parent' pats = do
parent <- liftIO $ canonicalizePath parent'
log LevelInfo $ toText $ "Traversing " <> parent <> " for files matching " <> show pats
liftIO $ getDirectoryFiles parent pats
data FileAction = Update | Delete
deriving (Eq, Show)
onChange ::
forall m.
(MonadIO m, MonadLogger m, MonadUnliftIO m) =>
FolderPath ->
(FilePath -> FileAction -> m ()) ->
m ()
onChange parent' f = do
-- NOTE: It is important to use canonical path, because this will allow us to
-- transform fsnotify event's (absolute) path into one that is relative to
-- @parent'@ (as passed by user), which is what @f@ will expect.
parent <- liftIO $ canonicalizePath parent'
withManagerM $ \mgr -> do
log LevelInfo $ toText $ "Monitoring " <> parent <> " for changes"
stop <- watchTreeM mgr parent (const True) $ \event -> do
log LevelDebug $ show event
let rel = makeRelative parent
case event of
Added (rel -> fp) _ _ -> f fp Update
Modified (rel -> fp) _ _ -> f fp Update
Removed (rel -> fp) _ _ -> f fp Delete
Unknown (rel -> fp) _ _ -> f fp Delete
liftIO $ threadDelay maxBound `finally` stop
withManagerM ::
(MonadIO m, MonadUnliftIO m) =>
(WatchManager -> m a) ->
m a
withManagerM f = do
withRunInIO $ \run ->
withManager $ \mgr -> run (f mgr)
watchTreeM ::
forall m.
(MonadIO m, MonadUnliftIO m) =>
WatchManager ->
FilePath ->
ActionPredicate ->
(Event -> m ()) ->
m StopListening
watchTreeM wm fp pr f =
withRunInIO $ \run ->
watchTree wm fp pr $ \evt -> run (f evt)