packages feed

robin-0.1.0.0: src/Watch.hs

{-# LANGUAGE OverloadedStrings #-}
module Watch
  ( WatchPred(..)
  , Watch(..)
  , emptyWatch
  , watchOnce
  , watchForever
  ) where

import Prelude hiding (FilePath)

import Control.Concurrent.Chan hiding (isEmptyChan)
import Control.Exception.Lifted
import Control.Monad.Reader
import Control.Monad.Trans.Control
import Data.Time.Clock
import System.FilePath
import System.FSNotify

import Types

data WatchPred =
    PredPath FilePath
  | PredName String
  | PredExtension String
  | PredAdded
  | PredModified
  | PredRemoved
  | PredConst Bool
  | PredConj [WatchPred]
  | PredDisj [WatchPred]
  | PredInverse WatchPred
  deriving (Show)

data Watch =
  Watch
  { wRecurse   :: Bool
  , wRootDir   :: FilePath
  , wPred      :: WatchPred
  } deriving (Show)

emptyWatch fp = Watch False fp (PredConst True)

eventFile :: Event -> FilePath
eventFile (Added fp _) = fp
eventFile (Modified fp _) = fp
eventFile (Removed fp _) = fp

wEvalPred :: Event -> WatchPred -> Bool
wEvalPred e (PredPath fp) = fp == eventFile e
wEvalPred e (PredName xs) = xs == takeFileName (eventFile e)
wEvalPred e (PredExtension xs) = '.' : xs == takeExtension (eventFile e)
wEvalPred (Added _ _) PredAdded = True
wEvalPred (Modified _ _) PredModified = True
wEvalPred (Removed _ _) PredRemoved = True
wEvalPred _ (PredConst b) = b
wEvalPred e (PredConj ps) = all (wEvalPred e) ps
wEvalPred e (PredDisj ps) = any (wEvalPred e) ps
wEvalPred e (PredInverse p) = not $ wEvalPred e p
wEvalPred _ _ = False

watchPrim
  :: (MonadBaseControl IO m, MonadIO m)
  => [Watch] -> (Chan Event -> m a) -> m a
watchPrim xs f = bracket (liftIO startManager) (liftIO . stopManager) $ \mgr -> do
  chan <- liftIO newChan
  let r x = if wRecurse x then watchTreeChan else watchDirChan
  let w x = r x mgr (wRootDir x) (\e -> wEvalPred e $ wPred x) chan
  liftIO $ mapM_ w xs
  f chan

readEvent :: (MonadIO m) => Chan Event -> m Event
readEvent chan = do
  event <- liftIO $ readChan chan
  debugs $ show event
  return event

watchOnce
  :: (MonadBaseControl IO m, MonadIO m)
  => [Watch] -> m a -> m a
watchOnce xs m = watchPrim xs $ \chan -> void (readEvent chan) >> m

watchForever
  :: (MonadBaseControl IO m, MonadIO m)
  => [Watch] -> m () -> m ()
watchForever xs m = watchPrim xs $ \chan -> forever $ do
  t <- liftIO $ getCurrentTime
  m
  void $ readEventNewerThan chan t

readEventNewerThan :: (MonadIO m) => Chan Event -> UTCTime -> m ()
readEventNewerThan chan t =
  readEvent chan
  >>= \e -> if eventTime e > t then return () else readEventNewerThan chan t