packages feed

pms-infra-procspawn-0.0.8.0: src/PMS/Infra/ProcSpawn/DS/Utility.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module PMS.Infra.ProcSpawn.DS.Utility where

import System.IO
import Control.Lens
import System.Exit
import System.Log.FastLogger
import qualified Control.Exception.Safe as E
import Control.Monad.IO.Class
import Control.Monad.Except
import Control.Monad.Reader
import qualified Control.Concurrent.STM as STM
import qualified System.Process as S
import qualified Data.ByteString as BS
import qualified System.Environment as Env
import qualified Data.ByteString.Char8 as BSC
import Control.Concurrent.Async

import qualified PMS.Domain.Model.DM.Type as DM
import qualified PMS.Domain.Model.DS.Utility as DM
import PMS.Infra.ProcSpawn.DM.Type

-- |
--
runApp :: DM.DomainData -> AppData -> TimedFastLogger -> AppContext a -> IO (Either DM.ErrorData a)
runApp domDat appDat logger ctx =
  DM.runFastLoggerT domDat logger
    $ runExceptT
    $ flip runReaderT domDat
    $ runReaderT ctx appDat


-- |
--
liftIOE :: IO a -> AppContext a
liftIOE f = liftIO (go f) >>= liftEither
  where
    go :: IO b -> IO (Either String b)
    go x = E.catchAny (Right <$> x) errHdl

    errHdl :: E.SomeException -> IO (Either String a)
    errHdl = return . Left . show

---------------------------------------------------------------------------------
-- |
--
toolsCallResponse :: STM.TQueue DM.McpResponse
                  -> DM.JsonRpcRequest
                  -> ExitCode
                  -> String
                  -> String
                  -> IO ()
toolsCallResponse resQ jsonRpc code outStr errStr = do
  let content = [ DM.McpToolsCallResponseResultContent "text" outStr
                , DM.McpToolsCallResponseResultContent "text" errStr
                ]
      result = DM.McpToolsCallResponseResult {
                  DM._contentMcpToolsCallResponseResult = content
                , DM._isErrorMcpToolsCallResponseResult = (ExitSuccess /= code)
                }
      resDat = DM.McpToolsCallResponseData jsonRpc result
      res = DM.McpToolsCallResponse resDat

  STM.atomically $ STM.writeTQueue resQ res

-- |
--
errorToolsCallResponse :: DM.JsonRpcRequest -> String -> AppContext ()
errorToolsCallResponse jsonRpc errStr = do
  let content = [ DM.McpToolsCallResponseResultContent "text" errStr ]
      result = DM.McpToolsCallResponseResult {
                  DM._contentMcpToolsCallResponseResult = content
                , DM._isErrorMcpToolsCallResponseResult = True
                }
      resDat = DM.McpToolsCallResponseData jsonRpc result
      res = DM.McpToolsCallResponse resDat

  resQ <- view DM.responseQueueDomainData <$> lift ask
  liftIOE $ STM.atomically $ STM.writeTQueue resQ res

-- |
--
runProc :: STM.TMVar (Maybe ProcData) -> String -> [String] -> [(String, String)] -> IO ()
runProc procVar cmd args addEnv = do

  hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.procRunTask.runProc start."

  (fromPtyHandle, toProcHandle) <- S.createPipe
  (fromProcHandle, toPtyHandle) <- S.createPipe
  -- (fromProcEHandle, toPtyEHandle) <- S.createPipe

  baseEnv <- Env.getEnvironment
  let cwd = Nothing
      runEnvs = Just $ baseEnv ++ addEnv

  hPutStrLn stderr $ "[INFO] env = " ++ show runEnvs
  hPutStrLn stderr $ "[INFO] cmd = " ++ cmd
  hPutStrLn stderr $ "[INFO] args = " ++ show args

  pHdl <- S.runProcess cmd args cwd runEnvs (Just fromPtyHandle) (Just toPtyHandle) (Just toPtyHandle)
  -- pHdl <- S.runProcess cmd args cwd runEnvs (Just fromPtyHandle) (Just toPtyHandle) (Just toPtyEHandle)
  let procData = ProcData {
                  _wHdLProcData = toProcHandle
                , _rHdlProcData = fromProcHandle
                , _eHdlProcData = fromProcHandle
--                , _eHdlProcData = fromProcEHandle
                , _pHdlProcData = pHdl
                }

  STM.atomically $ STM.putTMVar procVar (Just procData)

  hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.procRunTask.runProc end."


-- |
--   
readProc :: ProcData -> IO BS.ByteString
readProc dat = do
  let hdl = dat^.rHdlProcData
      timeoutMillis = 25000
  ready <- hWaitForInput hdl timeoutMillis
  if ready
    then BS.hGetSome hdl 1024
    else E.throwString $ "readProc: timeout waiting " ++ show timeoutMillis ++ " for input"


-- |
--   
readProc2 :: ProcData -> IO BS.ByteString
readProc2 dat = do
  let rHdl = dat^.rHdlProcData
      eHdl = dat^.eHdlProcData

  readProcHandle eHdl 1000 >>= \case
    Just eout -> readProcHandle rHdl 500 >>= \case
      Just erout -> do
        hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc eout,erout."
        return $ erout <> BSC.pack "\n" <> eout
      Nothing    -> do
        hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc eout,none."
        return eout
    Nothing   -> readProcHandle rHdl 20000 >>= \case
      Just rout -> readProcHandle eHdl 500 >>= \case
          Just reout -> do
            hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc none,rout,reout."
            return $ rout <> BSC.pack "\n" <> reout
          Nothing    -> do
            hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc none,rout,none."
            return rout
      Nothing   -> readProcHandle eHdl 500 >>= \case
          Just out -> do
            hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc none,none,out"
            return out
          Nothing  -> E.throwString $ "readProc: timeout waiting for stdout/stderr input"


-- |
--   
readProc3 :: ProcData -> IO BS.ByteString
readProc3 dat = do
  let rHdl = dat^.rHdlProcData
      eHdl = dat^.eHdlProcData

  hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc start race."
  res <- race (readProcHandle rHdl 25001)
              (readProcHandle eHdl 25002)

  hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc end race."
  case res of
    Left (Just out)  -> return out
    Right (Just err) -> return err
    _                -> E.throwString "readProc: timeout waiting for stdout/stderr input"


-- |
--   
readProcHandle :: Handle -> Int -> IO (Maybe BS.ByteString)
readProcHandle hdl timeoutMillis = do
  ready <- hWaitForInput hdl timeoutMillis
  if ready
    then Just <$> BS.hGetSome hdl 8192
    else return Nothing