module SOS where import System.FSNotify import System.Process import System.Exit import System.FilePath import System.Console.ANSI import Control.Monad import Control.Concurrent import Data.Maybe import Text.Regex.TDFA import Prelude hiding ( FilePath ) -- | A structure to hold our changed file events, -- list of commands to run and possibly the currently running process, -- in case it's one that hasn't terminated. data SOSState = SOSIdle | SOSPending { accumulatedEvents :: [Event] } | SOSRunning { runningProccess :: ProcessHandle , pendingCommands :: [String] } -- | Starts sos in a `dir` watching `ptns` to execute `cmds`. steelOverseer :: FilePath -> [String] -> [String] -> IO () steelOverseer dir cmds ptns = do putStrLn "Hit enter to quit.\n" wm <- startManager mvar <- newEmptyMVar let predicate = actionPredicateForRegexes ptns action = performCommand mvar cmds _ <- watchTree wm dir predicate action _ <- getLine cleanup wm mvar -- | Cleans up sos on close. cleanup :: WatchManager -> MVar SOSState -> IO () cleanup wm mvar = do putStrLn "Cleaning up." mState <- tryTakeMVar mvar case mState of Just (SOSRunning pid _) -> terminatePID pid _ -> return () stopManager wm actionPredicateForRegexes :: [String] -> Event -> Bool actionPredicateForRegexes ptns event = or (fmap (eventPath event =~) ptns :: [Bool]) performCommand :: MVar SOSState -> [String] -> Event -> IO () performCommand mvar cmds event = do cyanPrint event mSosState <- tryTakeMVar mvar sosState <- case mSosState of Nothing -> do -- This is the first file to change and there is no running process. startWriteProcess mvar cmds 1000000 return $ SOSPending [event] Just (SOSRunning pid _) -> do -- There is a hanging process. mExCode <- getProcessExitCode pid when (isNothing mExCode) $ terminatePID pid startWriteProcess mvar cmds 1000000 return $ SOSPending [event] Just (SOSPending events) -> -- SOS is waiting the 1sec delay for events before running the process. return $ SOSPending $ event:events Just state -> return state putMVar mvar sosState startWriteProcess :: MVar SOSState -> [String] -> Int -> IO () startWriteProcess mvar [] _ = do _ <- tryTakeMVar mvar return () startWriteProcess mvar (cmd:cmds) delay = void $ forkIO $ do threadDelay delay setSGR [SetColor Foreground Dull Green] putStr "\n> " setSGR [Reset] putStrLn cmd pId <- runCommand cmd mEvProcCurrent <- tryTakeMVar mvar case mEvProcCurrent of -- This shouldn't ever happen. Nothing -> putMVar mvar $ SOSRunning pId cmds Just _ -> void $ forkIO $ do putMVar mvar $ SOSRunning pId cmds exitCode <- waitForProcess pId case exitCode of ExitSuccess -> do greenPrint exitCode startWriteProcess mvar cmds 0 _ -> redPrint exitCode terminatePID :: ProcessHandle -> IO () terminatePID pid = do terminateProcess pid putStrLnColor Red "Terminated hanging process." cyanPrint :: Show a => a -> IO () cyanPrint = putStrLnColor Cyan . show redPrint :: Show a => a -> IO () redPrint = putStrLnColor Red . show greenPrint :: Show a => a -> IO () greenPrint = putStrLnColor Green . show putStrLnColor :: Color -> String -> IO () putStrLnColor c s = do setSGR [SetColor Foreground Dull c] putStrLn s setSGR [Reset]