delta-0.1.0.0: src/System/Delta/Callback.hs
{-# LANGUAGE GADTs #-}
module System.Delta.Callback ( CallbackWatcher
, CallbackId
-- * Construction
, withCallbacks
-- * Adding callbacks
, withDeleteCallback
, withChangedCallback
, withNewCallback
-- * Removing callbacks
, unregisterCallback
, removeAllCallbacks
-- * Closing the watcher
, closeCallbackWatcher
)where
import FRP.Sodium
import System.Delta.Base
import System.Delta.Class
import qualified Data.Map as M
import Control.Concurrent.MVar
newtype CallbackId = CallbackId Integer
deriving (Eq, Ord)
data CallbackWatcher w where
CallbackWatcher :: FileWatcher w => {
baseWatcher :: w
, nextCallbackId :: MVar CallbackId
, watcherCallbacks :: MVar (M.Map CallbackId (IO ()))
} -> CallbackWatcher w
-- | Raise the callback id of a callback watcher
raiseId :: CallbackWatcher w -> IO (CallbackId)
raiseId w = do
(CallbackId n) <- takeMVar $ nextCallbackId w
putMVar (nextCallbackId w) (CallbackId $ n+1)
return (CallbackId n)
-- | Add an action to unregister a callback
addCallbackUnregister :: CallbackWatcher w -> IO () -> IO CallbackId
addCallbackUnregister w removeCallback = do
newId <- raiseId w
mp <- takeMVar $ watcherCallbacks w
putMVar (watcherCallbacks w) (M.insert newId removeCallback mp)
return newId
-- | Wrap a file watcher in a datatype that allows adding callbacks
withCallbacks :: (FileWatcher a) => a -> IO (CallbackWatcher a)
withCallbacks w = do
nextIdVar <- newMVar (CallbackId 0)
callbacks <- newMVar (M.empty)
return $ CallbackWatcher w nextIdVar callbacks
-- | Add a callback that is executed when file deletion is detected
withDeleteCallback :: (FileWatcher a)
=> CallbackWatcher a
-> (FilePath -> IO ()) -- ^ An IO action on the deleted path
-> IO (CallbackId)
withDeleteCallback watcher action = do
unregisterCallback <- callbackOnEvent (deletedFiles $ baseWatcher watcher) action
addCallbackUnregister watcher unregisterCallback
-- | Add a callback that is executed when file creation is detected
withNewCallback :: (FileWatcher a)
=> CallbackWatcher a
-> (FilePath -> IO ()) -- ^ An IO action on the new path
-> IO (CallbackId)
withNewCallback watcher action = do
unregisterCallback <- callbackOnEvent (newFiles $ baseWatcher watcher) action
addCallbackUnregister watcher unregisterCallback
-- | Add a callback on a changed file
withChangedCallback :: (FileWatcher a)
=> CallbackWatcher a
-> (FileInfo -> IO ()) -- ^ Action on changed file
-> IO (CallbackId)
withChangedCallback watcher action = do
unregisterCallback <- callbackOnEvent (changedFiles $ baseWatcher watcher) action
addCallbackUnregister watcher unregisterCallback
-- | Unregister the given CallbackId from the FileWatcher
-- does nothing if the CallbackId is not in the watcher
unregisterCallback :: (FileWatcher a) => CallbackWatcher a -> CallbackId -> IO ()
unregisterCallback watcher cId = do
mp <- takeMVar $ watcherCallbacks watcher
case M.lookup cId mp of
Nothing -> return ()
Just action -> action
putMVar (watcherCallbacks watcher) (M.delete cId mp)
-- | Remove all callbacks form the watcher. They will not be called after this
removeAllCallbacks :: (FileWatcher a) => CallbackWatcher a -> IO ()
removeAllCallbacks watcher = do
mp <- takeMVar $ watcherCallbacks watcher
putMVar (watcherCallbacks watcher) M.empty
sequence_ (M.elems mp)
-- | Remove all callbacks and close the underlying FileWatcher
closeCallbackWatcher :: FileWatcher a => CallbackWatcher a -> IO ()
closeCallbackWatcher watcher = do
removeAllCallbacks watcher
cleanUpAndClose $ baseWatcher watcher
-- | Add a listener to an event, return the action to unregister the listener
callbackOnEvent :: Event a -> (a -> IO ()) -> IO (IO ())
callbackOnEvent e action = sync $ listen e action