lgtk-0.8: src/LGtk/Effects.hs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
module LGtk.Effects where
--import Control.Applicative
import Control.Concurrent
import Control.Exception (evaluate)
import Control.Monad
import Control.Monad.Fix
import Control.Monad.Operational
--import Control.Monad.Reader
import Control.Monad.Trans.Control
import System.Directory
import qualified System.FilePath as F
import System.FSNotify
import Filesystem.Path hiding (FilePath)
import Filesystem.Path.CurrentOS hiding (FilePath)
import qualified System.Environment as Env
import System.IO.Error (catchIOError, isDoesNotExistError)
import Data.LensRef
--------------------------------------------------------------------------
-- | Type class for IO actions.
class MonadRegister m => EffIORef m where
-- | The program's command line arguments (not including the program name).
getArgs :: m [String]
-- | The name of the program as it was invoked.
getProgName :: m String
-- | @(lookupEnv var)@ returns the value of the environment variable @var@.
lookupEnv :: String -> m (Maybe String)
{- |
@(asyncWrite t f a)@ has the effect of doing @(f a)@ after waiting @t@ milliseconds.
Note that @(asyncWrite 0 f a)@ acts immediately after the completion of the current computation,
so it is safe, because the effect of @(f a)@ is not interleaved with
the current computation.
Although @(asyncWrite 0)@ is safe, code using it has a bad small.
-}
asyncWrite :: Int -> Modifier m () -> m ()
{- |
@(fileRef path)@ returns a reference which holds the actual contents
of the file accessed by @path@.
When the value of the reference changes, the file changes.
When the file changes, the value of the reference changes.
If the reference holds @Nothing@, the file does not exist.
Note that you delete the file by putting @Nothing@ into the reference.
Implementation note: The references returned by @fileRef@ are not
memoised so currently it is unsafe to call @fileRef@ on the same filepath more than once.
This restriction will be lifted in the future.
-}
fileRef :: FilePath -> m (Ref m (Maybe String))
{- | Read a line from the standard input device.
@(getLine_ f)@ returns immediately. When the line @s@ is read,
@f s@ is called.
-}
getLine_ :: (String -> Modifier m ()) -> m ()
-- | Write a string to the standard output device.
putStr_ :: EffIORef m => String -> m ()
-- | @putStrLn_@ === @putStr_ . (++ "\n")@
putStrLn_ :: EffIORef m => String -> m ()
putStrLn_ = putStr_ . (++ "\n")
newtype Wrap m a = Wrap { unWrap :: Modifier m a }-- deriving (Functor, Applicative, Monad)
--deriving instance MonadRegister m => Functor (Wrap m)
--deriving instance MonadRegister m => Applicative (Wrap m)
deriving instance MonadRegister m => Monad (Wrap m)
instance (MonadRegister m, MonadFix (Modifier m)) => MonadFix (Wrap m) where
mfix f = Wrap $ mfix $ unWrap . f
instance (MonadRegister m) => MonadRefReader (Wrap m) where
type BaseRef (Wrap m) = BaseRef m
liftRefReader = Wrap . liftRefReader
instance MonadRegister m => MonadRefCreator (Wrap m) where
extRef r l = Wrap . extRef r l
newRef = Wrap . newRef
instance MonadRegister m => MonadMemo (Wrap m) where
memoRead (Wrap m) = liftM Wrap $ Wrap $ memoRead m
instance MonadRegister m => MonadRefWriter (Wrap m) where
liftRefWriter = Wrap . liftRefWriter
{-
deriving instance (MonadRegister m) => Monad (Modifier (Wrap m))
instance MonadRegister m => MonadRefReader (Modifier (Wrap m)) where
type BaseRef (Modifier (Wrap m)) = BaseRef m
liftRefReader = WrapM . liftRefReader
instance MonadRegister m => MonadMemo (Modifier (Wrap m)) where
memoRead (WrapM m) = liftM WrapM $ WrapM $ memoRead m
instance MonadRegister m => MonadRefCreator (Modifier (Wrap m)) where
extRef r l = WrapM . extRef r l
newRef = WrapM . newRef
instance MonadRegister m => MonadRefWriter (Modifier (Wrap m)) where
liftRefWriter = WrapM . liftRefWriter
-}
instance (MonadRegister m, MonadRegister (Modifier m)) => MonadRegister (Wrap m) where
type EffectM (Wrap m) = EffectM m
--newtype Modifier (Wrap m) a = WrapM { unWrapM :: Modifier m a}
type Modifier (Wrap m) = Wrap m
liftEffectM = Wrap . liftEffectM -- :: EffectM m a -> m a
liftToModifier = id --WrapM . liftToModifier . unWrap -- :: m a -> Modifier m a
-- onChangeAcc r b bc f = Wrap $ onChangeAcc r b bc $ (fmap . fmap . fmap) (liftM (fmap unWrap) . unWrap) f
onChange r f = Wrap $ onChange r $ fmap (liftM unWrap . unWrap) f
onChangeSimple r f = Wrap $ onChangeSimple r $ fmap unWrap f
registerCallback r = Wrap $ registerCallback (fmap unWrap r)
onRegionStatusChange g = Wrap $ onRegionStatusChange $ unWrap . g
data IOInstruction a where
GetArgs :: IOInstruction [String]
GetProgName :: IOInstruction String
LookupEnv :: String -> IOInstruction (Maybe String)
PutStr :: String -> IOInstruction ()
GetLine :: (String -> SIO ()) -> IOInstruction Handle
AsyncWrite :: Int -> SIO () -> IOInstruction Handle
FileRef :: FilePath -> (Maybe String -> SIO ()) -> IOInstruction (Handle, String -> SIO ())
type SIO = Program IOInstruction
type Handle = RegionStatusChange -> SIO ()
instance (MonadRegister m, MonadRegister (Modifier m), MonadBaseControl IO (EffectM m)) => EffIORef (Wrap m) where
getArgs = liftIO' Env.getArgs
getProgName = liftIO' Env.getProgName
-- lookupEnv = Env.lookupEnv -- does not work with Haskell Platform 2013.2.0.0
lookupEnv v = liftIO' $ catchIOError (liftM Just $ Env.getEnv v) $ \e ->
if isDoesNotExistError e then return Nothing else ioError e
asyncWrite t r = do
(u, f) <- liftEffectM forkIOs'
x <- registerCallback $ const r
onRegionStatusChange $ liftEffectM . u
liftEffectM $ f [ liftIO_ $ threadDelay t, x () ]
fileRef f = do
ms <- liftIO' r
ref <- newRef ms
v <- liftIO' newEmptyMVar
vman <- liftIO' $ newMVar $ return ()
cf <- liftIO' $ canonicalizePath' f
let
cf' = decodeString cf
g = (== cf')
h = tryPutMVar v () >> return ()
filt (Added x _) = g x
filt (Modified x _) = g x
filt (Removed x _) = g x
act (Added _ _) = h
act (Modified _ _) = h
act (Removed _ _) = h
startm = do
man <- startManager
putMVar vman $ stopManager man
watchDir man (directory cf') filt act
(u, ff) <- liftEffectM forkIOs'
re <- registerCallback (writeRef ref)
onRegionStatusChange $ liftEffectM . u
liftEffectM $ ff $ repeat $ liftIO_ (takeMVar v >> r) >>= re
_ <- onChangeSimple (readRef ref) $ \x -> liftIO' $ do
join $ takeMVar vman
_ <- tryTakeMVar v
w x
threadDelay 10000
startm
return ref
where
r = do
b <- doesFileExist f
if b then do
xs <- readFile f
_ <- evaluate (length xs)
return (Just xs)
else return Nothing
w = maybe (doesFileExist f >>= \b -> when b (removeFile f)) (writeFile f)
getLine_ w = do
(u, f) <- liftEffectM forkIOs'
x <- registerCallback w
onRegionStatusChange $ liftEffectM . u
liftEffectM $ f [ liftIO_ getLine >>= x ] -- TODO
putStr_ s = liftIO' $ putStr s
getLine__ :: (String -> IO ()) -> IO (RegionStatusChange -> IO ())
getLine__ f = do
_ <- forkIO $ forever $ getLine >>= f -- todo
return $ const $ return ()
-- canonicalizePath may fail if the file does not exsist
canonicalizePath' p = liftM (F.</> f) $ canonicalizePath d
where (d,f) = F.splitFileName p
liftIO' = liftEffectM . liftIO_
liftIO_ = liftBaseWith . const
forkIOs' :: MonadBaseControl IO m => m (RegionStatusChange -> m (), [m ()] -> m ())
forkIOs' = liftBaseWith $ \run -> do
x <- newMVar ()
s <- newEmptyMVar
let g = do
readMVar x
is <- takeMVar s
case is of
[] -> return ()
(i:is) -> do
putMVar s is
_ <- run i
g
f i Kill = killThread i
f _ Block = takeMVar x
f _ Unblock = putMVar x ()
i <- forkIO g
return (liftIO_ . f i, liftIO_ . putMVar s)