module SOS where import ANSIColors import System.FSNotify import System.Process import System.Exit import Control.Monad import Control.Concurrent import Data.Maybe import Filesystem.Path.CurrentOS as OS import Data.Text as T import Data.List as L import System.IO ( Handle ) import Prelude hiding ( FilePath ) type RunningProcess = IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -- | A tuple 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. type SOSState = ([Event], [String], Maybe ProcessHandle) steelOverseer :: [String] -> [String] -> IO () steelOverseer cmds exts = do putStrLn $ startMsg cmds exts wm <- startManager mvar <- newEmptyMVar let predicate = actionPredicateForExts $ L.map T.pack exts action = performCommand mvar cmds in watchTree wm curdir predicate action _ <- getLine mState <- tryTakeMVar mvar case mState of Just (_, _, Just pid) -> terminatePID pid _ -> return () stopManager wm putStrLn $ colorString ANSIGreen "Bye!" curdir :: OS.FilePath curdir = fromText $ T.pack "." actionPredicateForExts :: [Text] -> Event -> Bool actionPredicateForExts exts event = let maybeExt = extension $ eventPath event in case maybeExt of Just ext -> ext `elem` exts Nothing -> False performCommand :: MVar SOSState -> [String] -> Event -> IO () performCommand mvar cmds event = do cyanPrint event mEvProc <- tryTakeMVar mvar eventsAndProcess <- case mEvProc of Nothing -> do -- This is the first file to change and there is no running process. startWriteProcess mvar cmds 1000000 return ([event], cmds, Nothing) Just ([], _, Just pid) -> do -- There is a hanging process. mExCode <- getProcessExitCode pid when (isNothing mExCode) $ terminatePID pid startWriteProcess mvar cmds 1000000 return ([event], cmds, Nothing) Just (events, leftoverCmds, Nothing) -> -- SOS is waiting the 1sec delay for events before running the process. return (event:events, leftoverCmds, Nothing) Just enp -> return enp putMVar mvar eventsAndProcess startWriteProcess :: MVar SOSState -> [String] -> Int -> IO () startWriteProcess mvar [] _ = do _ <- tryTakeMVar mvar putStrLn $ colorString ANSIGreen "Done." startWriteProcess mvar (cmd:cmds) delay = void $ forkIO $ do threadDelay delay putStrLn $ colorString ANSIGreen "\n> " ++ cmd ++ "\n" pId <- runCommand cmd mEvProcCurrent <- tryTakeMVar mvar case mEvProcCurrent of Nothing -> putMVar mvar ([], cmds, Just pId) Just (_, _, _) -> void $ forkIO $ do putMVar mvar ([], cmds, Just pId) exitCode <- waitForProcess pId case exitCode of ExitSuccess -> do cyanPrint exitCode startWriteProcess mvar cmds 0 _ -> redPrint exitCode terminatePID :: ProcessHandle -> IO () terminatePID pid = do terminateProcess pid putStrLn $ colorString ANSIRed "Terminated hanging process." greenPrint :: (Show a) => a -> IO () greenPrint = colorPrint ANSIGreen cyanPrint :: (Show a) => a -> IO () cyanPrint = colorPrint ANSICyan redPrint :: (Show a) => a -> IO () redPrint = colorPrint ANSIRed colorPrint :: (Show a) => ANSIColor -> a -> IO () colorPrint c = putStrLn . colorString c . show startMsg :: [String] -> [String] -> String startMsg cmds exts = L.foldl (++) "" [ "Starting steeloverseer to perform " , L.intercalate ", " $ quote cmds , " when files of type " , L.intercalate ", " $ quote exts , " change in the current directory.\n" , "Hit enter to quit.\n" ] where quote = L.map (\s-> "\""++s++"\"")