content: {-# 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
  