packages feed

hydra-print-0.1.0.0: hydra-head.hs

-- | The client, goes with hydra-view.

module Main where

import Data.IORef
import Data.Word
import qualified Data.ByteString.Char8 as B
import Control.Monad       (when, forM_)
import Control.Concurrent (threadDelay)
import System.Console.GetOpt (getOpt, ArgOrder(Permute), OptDescr(Option), ArgDescr(..), usageInfo)
import System.Environment (getArgs, getEnvironment)
import System.IO          (IOMode(..), openFile, hClose, hPutStrLn)
import System.IO.Unsafe   (unsafePerformIO)
import System.Directory   (doesDirectoryExist, doesFileExist, removeFile)
import System.FilePath    ((</>),takeFileName)
import System.Posix.Files (createNamedPipe)
import System.Posix.Types (CMode(..))
import System.Process     (runInteractiveCommand)
import System.Exit        (exitFailure, exitSuccess)
import System.Random

import Prelude as P

import UI.HydraPrint (hydraPrint, defaultHydraConf)
import qualified System.IO.Streams as S
import System.IO.Streams.Concurrent (concurrentMerge)

--------------------------------------------------------------------------------

-- | Extra usage docs beyond the flag info.
usageStr :: String
usageStr = unlines $
 [ "\n Hydra-head works with 'hydra-view' to add a new stream."
 , " "   
 , " There are two main modes.  Hydra-head either returns immediately,"
 , " producing the name of a pipe you can use.  Or it pipes its "   
 , " standard input to the hydra-view session."
 , " "
 , " If you use the named pipe method, hydra-view will continue monitoring"
 , " the pipe until you explicitly DELETE it.  Thus the pipe can be used to "
 , " aggregate the output of multiple commands." 
 ]

-- | Datatype for command line options.
data Flag =
       ReturnPipe         -- | Return the name of a pipe immediately.
     | SessionID FilePath -- | Use a session ID to match-up with the view server.
     | ShowHelp       
  deriving (Eq,Read,Ord,Show)

isSessionID :: Flag -> Bool
isSessionID (SessionID _ ) = True
isSessionID _ = False

-- | Command line options.
cli_options :: [OptDescr Flag]
cli_options = 
     [ 
       Option ['s'] ["session"] (ReqArg SessionID "STRING")
       "Use a sessionID to avoid collision with other hydra-view servers."
     , Option ['p'] ["pipe"]    (NoArg  ReturnPipe)
       "Return immediately [the path of] a named pipe for subsequent use."
     , Option ['h'] ["help"]    (NoArg  ShowHelp)
       "Show help and exit."
     ]

--------------------------------------------------------------------------------
-- <DUPLICATED> FIXME, factor these out:

theEnv :: [(String, String)]
theEnv = unsafePerformIO getEnvironment

-- Here we make some attempt to work on Windows:
defaultTempDir :: String
defaultTempDir = unsafePerformIO $ do 
  b <- doesDirectoryExist "/tmp/"
  if b then return "/tmp/" else
    case lookup "TEMP" theEnv of
      Nothing -> error "hydra-view: Could not find a temporary directory to put the pipe source."
      Just d  -> return d

-- | There's a simple policy on where to put the pipes, so that other clients can
-- find it.
defaultPipeSrc :: String
defaultPipeSrc = defaultTempDir ++ "hydra-view.pipe"

sessionPipe :: String -> String
sessionPipe id = 
  defaultTempDir </> "hydra-view_session_"++id++".pipe"

-- </DUPLICATED>
--------------------------------------------------------------------------------


main :: IO ()
main = do
  cli_args <- getArgs
  let (options,restargs,errs) = getOpt Permute cli_options cli_args
      showUsage = do putStrLn "USAGE: hydra-head [OPTIONS] -- commands to run"
                     putStrLn$ usageStr
                     putStr$ usageInfo " OPTIONS:" cli_options                     
  when (not (null errs)) $ do
    putStrLn$ "Errors parsing command line options:" 
    mapM_ (putStr . ("   "++)) errs       
    showUsage
    exitFailure      
  when (ShowHelp `elem` options) $ do showUsage; exitSuccess

  let pipePerms = CMode 0o777
      openPipe p = do b <- doesFileExist p
                      when b $ removeFile p
                      createNamedPipe p pipePerms

      getName g = do let suffix :: Word64
                         (suffix,g') = random g
                     let path = defaultTempDir </> "hydra-head_tmp_pipe_"++show suffix
                     b <- doesFileExist path
                     if b then getName g'
                       else return path

      serverPipe = case filter isSessionID options of
                     []             -> defaultPipeSrc
                     [SessionID id] -> sessionPipe id
                     x  -> error "hydra-head: Multiple sessions specified: "++show x
                     
  stdGen <- getStdGen  
  newPipe <- getName stdGen
  openPipe newPipe

  -- Tell the server about the new pipe:
  hnd <- openFile serverPipe AppendMode
  hPutStrLn hnd newPipe 
  hClose hnd

  if ReturnPipe `elem` options then 
     putStrLn newPipe
   else do
    -- SO terribly hackish.  I get this error if I don't wait a bit:
-- hydra-head.exe: /tmp/hydra-head_tmp_pipe_14447421285220617689: openFile: does not exist (Device not configured)    
    threadDelay$ 10 * 1000
    outH <- openFile newPipe AppendMode
    out <- S.handleToOutputStream outH
    S.connect S.stdin out
    hClose outH
    removeFile newPipe
  return ()