packages feed

cachix-1.7: src/Cachix/Client/Daemon/Client.hs

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"