{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} -- | -- -- Copyright : (C) Keera Studios Ltd, 2013 -- License : BSD3 -- Maintainer : support@keera.co.uk module Hails.FS where import Prelude hiding (FilePath) import Control.Concurrent.MVar import Control.Concurrent (threadDelay, forkIO) import Control.Monad (forever) import Data.ReactiveValue import Filesystem.Path.CurrentOS import System.Directory import System.FSNotify -- | A file as a passive reactive value. -- -- Passive values are those that never notify of changes to them. They are -- useful as sources of information controlled by other RVs (buttons, etc.) pasiveFileReactive :: FilePath -> ReactiveFieldReadWrite IO String pasiveFileReactive fp = ReactiveFieldReadWrite setter getter notifier where getter = readFile (encodeString fp) setter v = writeFile (encodeString fp) v notifier _ = return () -- | A file as a reactive value. The file must exist at the time -- the call is evaluated. -- TODO: Make it ok for the file not to exist. -- TODO: Capture and ignore exceptions in readFile and writeFile. fileReactive :: FilePath -> IO (ReactiveFieldReadWrite IO String) fileReactive fp = do fpP <- canonicalizePath (encodeString fp) notifiers <- newMVar [] let getter = readFile (encodeString fp) -- fails if the path does not exist setter v = writeFile (encodeString fp) v -- may fail notify = sequence_ =<< readMVar notifiers notifier p = modifyMVar_ notifiers (\x -> return (x ++ [p])) -- Run the notification manager, ignore result (thread) forkIO $ withManager $ \mgr -> do _ <- watchDir mgr -- manager (encodeString $ directory fp) -- directory to watch (\e -> eventPath e == fpP) -- predicate (const notify) -- notifier forever $ threadDelay maxBound return $ ReactiveFieldReadWrite setter getter notifier