module Cachix.Client.Daemon.Client (push, stop) where import Cachix.Client.Daemon.Listen (getSocketPath) import Cachix.Client.Daemon.Protocol as Protocol import Cachix.Client.Env as Env import Cachix.Client.OptionsParser (DaemonOptions (..)) import qualified Control.Concurrent.Async as Async import Control.Exception.Safe (catchAny) import qualified Data.Aeson as Aeson import qualified Data.ByteString as BS import qualified Network.Socket as Socket import qualified Network.Socket.ByteString as Socket.BS import qualified Network.Socket.ByteString.Lazy as Socket.LBS import Protolude import qualified System.Posix.IO as Posix -- | Queue up push requests with the daemon -- -- TODO: wait for the daemon to respond that it has received the request push :: Env -> DaemonOptions -> [FilePath] -> IO () push _env daemonOptions storePaths = withDaemonConn (daemonSocketPath daemonOptions) $ \sock -> do Socket.LBS.sendAll sock (Aeson.encode pushRequest) where pushRequest = Protocol.ClientPushRequest $ PushRequest {storePaths = storePaths} -- | Tell the daemon to stop and wait for it to gracefully exit stop :: Env -> DaemonOptions -> IO () stop _env daemonOptions = withDaemonConn (daemonSocketPath daemonOptions) $ \sock -> do Async.concurrently_ (waitForResponse sock) $ Socket.LBS.sendAll sock (Aeson.encode Protocol.ClientStop) where waitForResponse sock = do -- Wait for the socket to close bs <- Socket.BS.recv sock 4096 `catchAny` (\_ -> return BS.empty) -- A zero-length response means that the daemon has closed the socket guard $ not $ BS.null bs case Aeson.eitherDecodeStrict bs of Left err -> putErrText (toS err) Right DaemonBye -> return () withDaemonConn :: Maybe FilePath -> (Socket.Socket -> IO a) -> IO a withDaemonConn optionalSocketPath f = do socketPath <- maybe getSocketPath pure optionalSocketPath bracket (open socketPath) Socket.close f `onException` failedToConnectTo socketPath where open socketPath = do sock <- Socket.socket Socket.AF_UNIX Socket.Stream Socket.defaultProtocol Socket.connect sock (Socket.SockAddrUnix socketPath) -- Network.Socket.accept sets the socket to non-blocking by default. Socket.withFdSocket sock $ \fd -> Posix.setFdOption (fromIntegral fd) Posix.NonBlockingRead False return sock failedToConnectTo :: FilePath -> IO () failedToConnectTo socketPath = do putErrText "Failed to connect to Cachix Daemon" putErrText $ "Tried to connect to: " <> toS socketPath <> "\n"