zng_ext_setup/
sfx_client.rs1use std::{fmt, io, path::Path};
2
3use zng_txt::{ToTxt, Txt};
4
5#[derive(Clone, PartialEq, Debug)]
7pub struct SfxClient {
8 sfx_args: Box<[Txt]>,
9 manifest: Box<[SfxDataInfo]>,
10}
11impl SfxClient {
12 pub fn can_connect() -> bool {
16 match std::env::var("SFX_ARGS") {
17 Ok(_) => true,
18 Err(e) => !SfxError::from(e).is_no_sfx(),
19 }
20 }
21
22 pub fn connect_blocking() -> Result<Self, SfxError> {
24 let args = std::env::var("SFX_ARGS")?;
26 let mut sfx_args = vec![];
27 for arg in args.split('\n') {
28 let arg = arg.trim();
29 if arg.is_empty() {
30 continue;
31 }
32 sfx_args.push(Txt::from_str(arg));
33 }
34
35 if sfx_args.is_empty() {
36 return Err(io::Error::new(io::ErrorKind::InvalidFilename, "SFX_ARGS is empty").into());
37 }
38
39 let out = std::process::Command::new(&sfx_args[0]).env("SFX_GET_MANIFEST", "").output()?;
41 if !out.status.success() {
42 let err = String::from_utf8_lossy(&out.stderr);
43 return Err(io::Error::other(format!("SFX_GET_MANIFEST failed\ncode: {:?}\nstderr:\n{}", out.status, err)).into());
44 }
45 let stdout = match String::from_utf8(out.stdout) {
46 Ok(s) => s,
47 Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e).into()),
48 };
49 let mut manifest = vec![];
50 for line in stdout.lines() {
51 let mut ok = line.is_empty();
52 if let Some((name, len)) = line.rsplit_once(':') {
53 if let Ok(len) = len.parse() {
54 ok = true;
55 manifest.push(SfxDataInfo {
56 name: name.to_txt(),
57 exact_len: Some(len),
58 });
59 } else if len == "unknown" {
60 ok = true;
61 manifest.push(SfxDataInfo {
62 name: name.to_txt(),
63 exact_len: None,
64 });
65 }
66 }
67 if !ok {
68 return Err(io::Error::new(io::ErrorKind::InvalidData, format!("unexpected manifest format, {line:?}")).into());
69 }
70 }
71 manifest.sort_by(|a, b| a.name.cmp(&b.name));
72 manifest.dedup_by(|a, b| a.name == b.name);
73
74 Ok(Self {
75 sfx_args: sfx_args.into_boxed_slice(),
76 manifest: manifest.into_boxed_slice(),
77 })
78 }
79
80 pub async fn connect() -> Result<Self, SfxError> {
82 zng_task::wait(Self::connect_blocking).await
83 }
84
85 pub fn sfx_exe(&self) -> &Path {
89 Path::new(&self.sfx_args[0])
90 }
91
92 pub fn sfx_args(&self) -> &[Txt] {
99 &self.sfx_args
100 }
101
102 pub fn manifest(&self) -> &[SfxDataInfo] {
106 &self.manifest
107 }
108
109 pub fn info(&self, name: &str) -> Option<&SfxDataInfo> {
111 let i = self.manifest.binary_search_by_key(&name, |n| n.name.as_str()).ok()?;
112 Some(&self.manifest[i])
113 }
114
115 pub fn read_blocking(&self, name: &str) -> Result<SfxReadBlocking, SfxError> {
119 if let Some(info) = self.info(name) {
120 let mut r = std::process::Command::new(self.sfx_exe()).env("SFX_GET_DATA", name).spawn()?;
121 r.stdin = None;
122 Ok(SfxReadBlocking(DefaultSfxReadBlocking {
123 server: r,
124 exact_len: info.exact_len,
125 }))
126 } else {
127 Err(SfxError::NotFound(Txt::from_str(name)))
128 }
129 }
130
131 pub async fn read(&self, name: &str) -> Result<SfxRead, SfxError> {
135 if let Some(info) = self.info(name) {
136 let mut cmd = std::process::Command::new(self.sfx_exe());
137 cmd.env("SFX_GET_DATA", name);
138 let exact_len = info.exact_len;
139 zng_task::wait(move || {
140 let mut r = cmd.spawn()?;
141 r.stdin = None;
142 let inner = SfxReadBlocking(DefaultSfxReadBlocking { server: r, exact_len });
143 Ok(SfxRead {
144 inner: zng_task::io::Unblock::new(inner),
145 })
146 })
147 .await
148 } else {
149 Err(SfxError::NotFound(Txt::from_str(name)))
150 }
151 }
152}
153
154#[derive(Clone, PartialEq, Debug)]
156#[non_exhaustive]
157pub struct SfxDataInfo {
158 pub name: Txt,
160
161 pub exact_len: Option<u64>,
166}
167
168pub struct SfxReadBlocking(DefaultSfxReadBlocking);
172impl SfxReadBlocking {
173 pub fn exact_len(&self) -> Option<u64> {
177 self.0.exact_len
178 }
179}
180struct DefaultSfxReadBlocking {
181 server: std::process::Child,
183 exact_len: Option<u64>,
184}
185impl io::Read for DefaultSfxReadBlocking {
186 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
187 if let Some(s) = &mut self.server.stdout {
188 let n = s.read(buf)?;
189 if n == 0 {
190 self.server.stdout = None;
191 self.server.kill()?;
192 let status = self.server.wait()?;
193 if !status.success() {
194 let mut stderr = String::new();
195 if let Some(mut s) = self.server.stderr.take() {
196 s.read_to_string(&mut stderr)?;
197 }
198 return Err(io::Error::new(
199 io::ErrorKind::ConnectionAborted,
200 format!("server failed\nexit code: {status:?}\nstderr:\n{stderr}"),
201 ));
202 }
203 if let Some(l) = self.exact_len
204 && l > 0
205 {
206 return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "stdout ended before exact length"));
207 }
208 } else if let Some(l) = &mut self.exact_len {
209 let n = n as u64;
210 if *l >= n {
211 *l -= n;
212 } else {
213 self.server.stdout = None;
214 let _ = self.server.kill();
215 return Err(io::Error::other("stdout longer than expected exact length"));
216 }
217 }
218 Ok(n)
219 } else {
220 Ok(0)
221 }
222 }
223}
224impl io::Read for SfxReadBlocking {
225 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
226 self.0.read(buf)
227 }
228
229 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
230 if let Some(l) = self.0.exact_len {
231 let l = l.min(usize::MAX as u64) as usize;
232 buf.try_reserve(l)?;
233 }
234 self.0.read_to_end(buf)
235 }
236
237 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
238 if let Some(l) = self.0.exact_len {
239 let l = l.min(usize::MAX as u64) as usize;
240 buf.try_reserve(l)?;
241 }
242 self.0.read_to_string(buf)
243 }
244}
245impl Drop for SfxReadBlocking {
246 fn drop(&mut self) {
247 if let Err(e) = self.0.server.kill() {
248 tracing::error!("cannot kill server on drop, {e}");
249 }
250 }
251}
252
253pub struct SfxRead {
257 inner: zng_task::io::Unblock<SfxReadBlocking>,
258}
259impl SfxRead {
260 pub async fn exact_len(&mut self) -> Option<u64> {
264 self.inner.get_mut().await.0.exact_len
265 }
266
267 pub async fn into_blocking(self) -> SfxReadBlocking {
269 self.inner.into_inner().await
270 }
271}
272impl zng_task::io::AsyncRead for SfxRead {
273 fn poll_read(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut [u8]) -> std::task::Poll<io::Result<usize>> {
274 std::pin::pin!(&mut self.get_mut().inner).poll_read(cx, buf)
275 }
276}
277
278#[derive(Debug)]
280#[non_exhaustive]
281pub enum SfxError {
282 Var(std::env::VarError),
284 Io(io::Error),
286 NotFound(Txt),
288}
289impl SfxError {
290 pub fn is_no_sfx(&self) -> bool {
294 matches!(self, Self::Var(std::env::VarError::NotPresent))
295 }
296}
297impl From<std::env::VarError> for SfxError {
298 fn from(var: std::env::VarError) -> Self {
299 Self::Var(var)
300 }
301}
302impl From<io::Error> for SfxError {
303 fn from(e: io::Error) -> Self {
304 Self::Io(e)
305 }
306}
307impl fmt::Display for SfxError {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 match self {
310 SfxError::Var(e) => fmt::Display::fmt(e, f),
311 SfxError::Io(e) => fmt::Display::fmt(e, f),
312 SfxError::NotFound(name) => write!(f, "no {name:?} data on the server catalog"),
313 }
314 }
315}
316impl std::error::Error for SfxError {
317 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
318 match self {
319 SfxError::Var(e) => Some(e),
320 SfxError::Io(e) => Some(e),
321 SfxError::NotFound(_) => None,
322 }
323 }
324}