libssh2-conduit-0.2.1: Network/SSH/Client/LibSSH2/Conduit.hs
{-# LANGUAGE FlexibleContexts #-}
module Network.SSH.Client.LibSSH2.Conduit
(sourceChannel,
CommandsHandle,
execCommand,
getReturnCode
) where
import Control.Monad
import Control.Monad.IO.Class (MonadIO (..))
import Control.Concurrent.STM
import Data.Conduit
import qualified Data.ByteString as B
import Network.SSH.Client.LibSSH2.Foreign
import Network.SSH.Client.LibSSH2
-- | Read all contents of libssh2's Channel.
sourceChannel :: MonadIO m => Channel -> Source m B.ByteString
sourceChannel ch = src
where
src = do
res <- liftIO $ readChannel ch 0x400
if B.length res > 0
then do
yield res
src
else return ()
-- | Execute one command and read it's output lazily.
-- If first argument is True, then you *must* get return code
-- using getReturnCode on returned CommandsHandle. Moreover,
-- you *must* guarantee that getReturnCode will be called
-- only when all command output will be read.
execCommand :: MonadIO m
=> Bool -- ^ Set to True if you want to get return code when command will terminate.
-> Session
-> String -- ^ Command
-> IO (Maybe CommandsHandle, Source m B.ByteString)
execCommand b s cmd = do
(ch, channel) <- initCH b s
let src = execCommandSrc ch channel cmd
return (if b then Just ch else Nothing, src)
-- | Handles channel opening and closing.
data CommandsHandle = CommandsHandle {
chReturnCode :: Maybe (TMVar Int),
chChannel :: TMVar Channel,
chChannelClosed :: TVar Bool }
initCH :: Bool -> Session -> IO (CommandsHandle, Channel)
initCH False s = do
c <- newTVarIO False
ch <- newEmptyTMVarIO
channel <- openCH ch s
return (CommandsHandle Nothing ch c, channel)
initCH True s = do
r <- newEmptyTMVarIO
c <- newTVarIO False
ch <- newEmptyTMVarIO
channel <- openCH ch s
return (CommandsHandle (Just r) ch c, channel)
openCH :: TMVar Channel -> Session -> IO Channel
openCH var s = do
ch <- openChannelSession s
atomically $ putTMVar var ch
return ch
-- | Get return code for previously run command.
-- It will fail if command was run using execCommand False.
-- Should be called only when all command output is read.
getReturnCode :: CommandsHandle -> IO Int
getReturnCode ch = do
c <- atomically $ readTVar (chChannelClosed ch)
if c
then do
case chReturnCode ch of
Nothing -> fail "Channel already closed and no exit code return was set up for command."
Just v -> atomically $ takeTMVar v
else do
channel <- atomically $ takeTMVar (chChannel ch)
cleanupChannel ch channel
atomically $ writeTVar (chChannelClosed ch) True
case chReturnCode ch of
Nothing -> fail "No exit code return was set up for commnand."
Just v -> do
rc <- atomically $ takeTMVar v
return rc
execCommandSrc :: MonadIO m => CommandsHandle -> Channel -> String -> Source m B.ByteString
execCommandSrc var channel command = src
where
src = do
liftIO $ channelExecute channel command
pullAnswer channel
pullAnswer ch = do
res <- liftIO $ readChannel ch 0x400
if B.length res > 0
then do
yield res
pullAnswer ch
else do
liftIO $ cleanupChannel var ch
return ()
-- | Close Channel and write return code
cleanupChannel :: CommandsHandle -> Channel -> IO ()
cleanupChannel ch channel = do
c <- atomically $ readTVar (chChannelClosed ch)
when (not c) $ do
closeChannel channel
case chReturnCode ch of
Nothing -> return ()
Just v -> do
exitStatus <- channelExitStatus channel
atomically $ putTMVar v exitStatus
closeChannel channel
freeChannel channel
atomically $ writeTVar (chChannelClosed ch) True
return ()