simploxide_ws_core/
cli.rs1pub use simploxide_core::SimplexVersion;
2
3use tokio::process::{Child, Command};
4
5use std::{
6 ffi::OsString,
7 io,
8 iter::{Chain, Empty, Once},
9 process::Stdio,
10};
11
12pub struct SimplexCli {
22 handle: Option<Child>,
23 port: u16,
24 version: SimplexVersion,
25}
26
27impl SimplexCli {
28 const MIN_SUPPORTED_VERSION: SimplexVersion = simploxide_core::MIN_SUPPORTED_VERSION;
29 const MAX_SUPPORTED_VERSION: SimplexVersion = simploxide_core::MAX_SUPPORTED_VERSION;
30
31 pub fn builder(default_bot_name: impl Into<String>, port: u16) -> SimplexCliBuilder {
35 SimplexCliBuilder {
36 port,
37 default_bot_name: default_bot_name.into(),
38 db_path: "bot".into(),
39 db_key: None,
40 extra_args: std::iter::empty(),
41 }
42 }
43
44 pub fn port(&self) -> u16 {
45 self.port
46 }
47
48 pub fn version(&self) -> &SimplexVersion {
49 &self.version
50 }
51
52 pub async fn kill(&mut self) -> io::Result<()> {
54 if let Some(mut handle) = self.handle.take() {
55 handle.kill().await?;
56 }
57
58 Ok(())
59 }
60}
61
62impl Drop for SimplexCli {
63 fn drop(&mut self) {
64 if let Some(ref mut handle) = self.handle {
65 if handle.try_wait().ok().flatten().is_none() {
69 let _ = handle.start_kill();
70 let _ = handle.try_wait();
71 }
72 }
73 }
74}
75
76pub struct SimplexCliBuilder<I = Empty<OsString>> {
90 port: u16,
91 default_bot_name: String,
92 db_path: String,
93 db_key: Option<String>,
94 extra_args: I,
95}
96
97impl<I> SimplexCliBuilder<I>
98where
99 I: Iterator<Item = OsString>,
100{
101 pub fn db_prefix(mut self, path: impl Into<String>) -> Self {
103 self.db_path = path.into();
104 self
105 }
106
107 pub fn db_key(mut self, key: impl Into<String>) -> Self {
109 self.db_key = Some(key.into());
110 self
111 }
112
113 pub fn arg(self, arg: impl Into<OsString>) -> SimplexCliBuilder<Chain<I, Once<OsString>>> {
115 SimplexCliBuilder {
116 port: self.port,
117 default_bot_name: self.default_bot_name,
118 db_path: self.db_path,
119 db_key: self.db_key,
120 extra_args: self.extra_args.chain(std::iter::once(arg.into())),
121 }
122 }
123
124 pub fn args<J>(self, args: J) -> SimplexCliBuilder<Chain<I, J::IntoIter>>
126 where
127 J: IntoIterator<Item = OsString>,
128 {
129 SimplexCliBuilder {
130 port: self.port,
131 default_bot_name: self.default_bot_name,
132 db_path: self.db_path,
133 db_key: self.db_key,
134 extra_args: self.extra_args.chain(args),
135 }
136 }
137
138 pub async fn spawn(self) -> io::Result<SimplexCli> {
142 let sxc_cmd = if std::path::Path::new("./simplex-chat").exists() {
143 "./simplex-chat"
144 } else {
145 "simplex-chat"
146 };
147
148 let version_output = Command::new(sxc_cmd).arg("--version").output().await?;
149
150 let output_str = String::from_utf8(version_output.stdout)
151 .map_err(|_| io::Error::other("simplex-chat --version returned invalid string"))?;
152
153 let version_str = output_str
154 .lines()
155 .next()
156 .and_then(|line| line.trim().strip_prefix("SimpleX Chat v"))
157 .ok_or_else(|| {
158 io::Error::other(format!("Cannot parse SimpleX Chat version: {output_str:?}"))
159 })?;
160
161 let version: SimplexVersion = version_str.parse().map_err(|_| {
162 io::Error::other(format!(
163 "Cannot parse SimpleX Chat version: {version_str:?}"
164 ))
165 })?;
166
167 if !version.is_supported() {
168 return Err(io::Error::other(format!(
169 "The Simplex CLI {version} is incompatible with current simploxide version\n\
170 Supported CLI versions: {}...{}",
171 SimplexCli::MIN_SUPPORTED_VERSION,
172 SimplexCli::MAX_SUPPORTED_VERSION
173 )));
174 }
175
176 let mut cmd = Command::new(sxc_cmd);
177 cmd.stdin(Stdio::null())
178 .stdout(Stdio::null())
179 .stderr(Stdio::null());
180
181 #[cfg(unix)]
186 cmd.process_group(0);
187
188 cmd.arg("-d")
189 .arg(&self.db_path)
190 .arg("-p")
191 .arg(self.port.to_string())
192 .arg("--create-bot-display-name")
193 .arg(&self.default_bot_name);
194
195 if let Some(ref key) = self.db_key {
196 cmd.arg("-k").arg(key);
197 }
198
199 cmd.args(self.extra_args);
200
201 let mut handle = cmd.spawn()?;
202
203 if let Ok(ret) =
204 tokio::time::timeout(std::time::Duration::from_secs(2), handle.wait()).await
205 {
206 let exit = ret?;
207 return Err(io::Error::other(format!(
208 "SimpleX-CLI terminated unexpectedly: {exit}"
209 )));
210 }
211
212 Ok(SimplexCli {
213 handle: Some(handle),
214 port: self.port,
215 version,
216 })
217 }
218}