packages feed

funflow-1.0.0: src/Control/Funflow/ContentStore/Notify/BSD.hs

{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Implementation of filesystem watch for BSD-based systems using KQueue.
module Control.Funflow.ContentStore.Notify.BSD
  ( Notifier
  , initNotifier
  , killNotifier

  , Watch
  , addDirWatch
  , removeDirWatch
  ) where

import           Control.Concurrent
import           Control.Concurrent.Async
import           Control.Exception.Safe
import           Control.Monad
import           Data.Typeable
import           Foreign.Ptr
import           System.KQueue
import           System.Posix.IO
import           System.Posix.Types

type Notifier = KQueue

data NotifierException
  = RequiresThreadedRuntime
  deriving (Show, Typeable)
instance Exception NotifierException where
  displayException = \case
    -- XXX: Compile time check?
    RequiresThreadedRuntime ->
      "Threaded runtime required! Please rebuild with -threaded flag."

initNotifier :: IO Notifier
initNotifier = do
  unless rtsSupportsBoundThreads $ throwIO RequiresThreadedRuntime
  kqueue

killNotifier :: Notifier -> IO ()
killNotifier _ = return ()

data Watch = Watch KQueue Fd (Async ())

addDirWatch :: Notifier -> FilePath -> IO () -> IO Watch
addDirWatch kq dir f = do
  fd <- openFd dir ReadOnly Nothing defaultFileFlags
  a <- async $ do
    let event = KEvent
          { ident = fromIntegral fd
          , evfilter = EvfiltVnode
          , flags = [EvAdd]
          , fflags = [NoteDelete, NoteAttrib, NoteRename, NoteRevoke]
          , data_ = 0
          , udata = nullPtr
          }
        loop = do
          chgs <- kevent kq [event] 1 Nothing
          unless (null chgs) f
          loop
    loop `finally` closeFd fd
  return $! Watch kq fd a

removeDirWatch :: Watch -> IO ()
removeDirWatch (Watch kq fd a) = do
  closeFd fd
  let event = KEvent
        { ident = fromIntegral fd
        , evfilter = EvfiltVnode
        , flags = [EvDelete]
        , fflags = []
        , data_ = 0
        , udata = nullPtr
        }
  void (kevent kq [event] 0 Nothing)
    `catch` \(_ :: KQueueException) -> return ()
  cancel a