tokio_hglib/
client.rs

1//! Functions to interact with Mercurial command server.
2
3use std::io;
4use std::path::Path;
5use std::process::Stdio;
6use tokio::process::Command;
7
8use crate::codec::ChannelMessage;
9use crate::connection::{Connection, PipeConnection};
10use crate::message::{self, ServerSpec};
11use crate::protocol::Protocol;
12use crate::runcommand::{self, UiHandler};
13
14/// Command-server client holding active connection.
15#[derive(Debug)]
16pub struct Client<C> {
17    proto: Protocol<C>,
18    spec: ServerSpec,
19}
20
21impl<C> Client<C> {
22    fn new(proto: Protocol<C>, spec: ServerSpec) -> Self {
23        Self { proto, spec }
24    }
25
26    /// Server capabilities, encoding, etc.
27    pub fn server_spec(&self) -> &ServerSpec {
28        &self.spec
29    }
30
31    /// Mutably borrows the underlying protocol.
32    ///
33    /// This is a low-level interface to implement new command handler.
34    pub fn borrow_protocol_mut(&mut self) -> &mut Protocol<C> {
35        &mut self.proto
36    }
37}
38
39impl<C> Client<C>
40where
41    C: Connection,
42{
43    /// Runs the Mercurial command specified in bytes.
44    ///
45    /// # Panics
46    ///
47    /// Panics if argument contains `\0` character.
48    pub async fn run_command(
49        &mut self,
50        handler: &mut impl UiHandler,
51        args: impl IntoIterator<Item = impl AsRef<[u8]>>,
52    ) -> io::Result<i32> {
53        runcommand::run_command(
54            self.borrow_protocol_mut(),
55            handler,
56            message::pack_args(args),
57        )
58        .await
59    }
60}
61
62/// Command-server client which spawns new server process and interacts via pipe.
63pub type PipeClient = Client<PipeConnection>;
64
65impl PipeClient {
66    /// Spawns a server process at the specified directory with the default configuration.
67    pub async fn spawn_at(dir: impl AsRef<Path>) -> io::Result<Self> {
68        let mut command = Command::new("hg");
69        command
70            .args(&[
71                "serve",
72                "--cmdserver",
73                "pipe",
74                "--config",
75                "ui.interactive=True",
76            ])
77            .current_dir(dir)
78            .env("HGPLAIN", "1");
79        PipeClient::spawn_with(&mut command).await
80    }
81
82    /// Spawns a server process by the given process builder.
83    pub async fn spawn_with(command: &mut Command) -> io::Result<Self> {
84        let child = command
85            .stdin(Stdio::piped())
86            .stdout(Stdio::piped())
87            .spawn()?;
88        let mut proto = Protocol::new(PipeConnection::new(child));
89        let spec = read_hello(&mut proto).await?;
90        Ok(Self::new(proto, spec))
91    }
92
93    // TODO: shuts down the process cleanly?
94}
95
96#[cfg(unix)]
97pub use self::unix::UnixClient;
98
99#[cfg(unix)]
100mod unix {
101    use std::ffi::OsStr;
102    use std::os::unix::io::{AsRawFd, RawFd};
103    use tokio::net::UnixStream;
104
105    use super::*;
106    use crate::connection::UnixConnection;
107
108    impl<C> Client<C>
109    where
110        C: Connection,
111    {
112        /// Runs the Mercurial command specified in platform string.
113        pub async fn run_command_os(
114            &mut self,
115            handler: &mut impl UiHandler,
116            args: impl IntoIterator<Item = impl AsRef<OsStr>>,
117        ) -> io::Result<i32> {
118            runcommand::run_command(
119                self.borrow_protocol_mut(),
120                handler,
121                message::pack_args_os(args),
122            )
123            .await
124        }
125    }
126
127    impl<C> AsRawFd for Client<C>
128    where
129        C: AsRawFd,
130    {
131        fn as_raw_fd(&self) -> RawFd {
132            self.proto.as_raw_fd()
133        }
134    }
135
136    /// Command-server client which interacts via Unix domain socket.
137    pub type UnixClient = Client<UnixConnection>;
138
139    impl UnixClient {
140        /// Connects to a command server listening at the specified socket path.
141        pub async fn connect(path: impl AsRef<Path>) -> io::Result<Self> {
142            let stream = UnixStream::connect(path).await?;
143            let mut proto = Protocol::new(UnixConnection::new(stream));
144            let spec = read_hello(&mut proto).await?;
145            Ok(Self::new(proto, spec))
146        }
147    }
148}
149
150async fn read_hello(proto: &mut Protocol<impl Connection>) -> io::Result<ServerSpec> {
151    match proto.fetch_response().await? {
152        ChannelMessage::Data(b'o', data) => message::parse_hello(data),
153        _ => Err(io::Error::new(
154            io::ErrorKind::InvalidData,
155            "no hello message received",
156        )),
157    }
158}