1use crate::{Diagnostic, Failure, invalid, process::ProcessGuard};
2use anyhow::{Context, Result, ensure};
3use serde_json::{Value, json};
4use std::{path::PathBuf, process::Stdio, time::Duration};
5use tokio::{
6 io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
7 process::{Child, ChildStdin, ChildStdout, Command},
8};
9use tokio_util::sync::CancellationToken;
10
11#[derive(Clone)]
12pub struct Runtime {
13 pub directory: PathBuf,
14 pub sdk: PathBuf,
15 pub simulate: bool,
16 pub sdk_simulation: Option<Value>,
17}
18impl Runtime {
19 pub async fn usb_command(&self, args: &[&str], seconds: u64) -> Result<String> {
20 ensure!(
21 !self.simulate,
22 "Simulation must not launch USB recovery helpers"
23 );
24 let mut command = Command::new(
25 self.directory
26 .join(format!("regain-device{}", std::env::consts::EXE_SUFFIX)),
27 );
28 command
29 .args(args)
30 .stdin(Stdio::null())
31 .stdout(Stdio::piped())
32 .stderr(Stdio::piped())
33 .kill_on_drop(true);
34 #[cfg(windows)]
35 command.creation_flags(0x08000000);
36 let output = tokio::time::timeout(Duration::from_secs(seconds), command.output())
37 .await
38 .context("USB helper deadline exceeded")??;
39 ensure!(
40 output.status.success(),
41 "USB helper failed ({}; Windows may require administrator approval): {}",
42 output.status,
43 String::from_utf8_lossy(&output.stderr).trim()
44 );
45 Ok(String::from_utf8(output.stdout)?.trim().to_owned())
46 }
47 pub async fn spawn(&self, direct: bool, log: Diagnostic) -> Result<Worker> {
48 let path = self
49 .directory
50 .join(format!("regain-device{}", std::env::consts::EXE_SUFFIX));
51 let mut command = Command::new(&path);
52 command
53 .args([
54 "zwo",
55 if direct {
56 "camera-direct"
57 } else {
58 "camera-sdk"
59 },
60 ])
61 .stdin(Stdio::piped())
62 .stdout(Stdio::piped())
63 .stderr(Stdio::piped())
64 .kill_on_drop(true)
65 .current_dir(&self.directory);
66 #[cfg(windows)]
67 command.creation_flags(0x08000000);
68 if direct {
69 command.arg("--serve");
70 }
71 if self.simulate {
72 command.arg("--simulate");
73 } else if !direct {
74 command.arg("--sdk").arg(&self.sdk);
75 }
76 let mut child = command
77 .spawn()
78 .with_context(|| format!("Start {}", path.display()))?;
79 let pid = child.id().context("Worker has no process ID")?;
80 let guard = ProcessGuard::attach(pid)?;
81 let stderr = child.stderr.take().context("Worker stderr unavailable")?;
82 let diagnostic = tokio::spawn(async move {
83 let mut lines = BufReader::new(stderr).lines();
84 while let Ok(Some(line)) = lines.next_line().await {
85 if let Some(text) = line.strip_prefix("REGAIN_DIAGNOSTIC ")
86 && let Ok(record) = serde_json::from_str::<Value>(text)
87 {
88 log(
89 record["level"].as_str().unwrap_or("info"),
90 record["event"].as_str().unwrap_or("worker"),
91 record["message"].as_str().unwrap_or(&line),
92 );
93 } else {
94 log("info", "worker", &line);
95 }
96 }
97 });
98 let mut worker = Worker {
99 input: child.stdin.take().context("Worker stdin unavailable")?,
100 output: child.stdout.take().context("Worker stdout unavailable")?,
101 child,
102 _guard: guard,
103 diagnostic,
104 id: 0,
105 };
106 if self.simulate
107 && !direct
108 && let Some(settings) = &self.sdk_simulation
109 {
110 worker
111 .call(
112 "simulation",
113 settings.clone(),
114 5.,
115 &CancellationToken::new(),
116 )
117 .await?;
118 }
119 Ok(worker)
120 }
121 pub async fn list(
126 &self,
127 direct: bool,
128 log: Diagnostic,
129 token: &CancellationToken,
130 ) -> Result<Value> {
131 let mut worker = self.spawn(direct, log).await?;
132 let result = worker
133 .call("list", Value::Null, 20., token)
134 .await
135 .map(|r| r.0);
136 worker.kill().await;
137 result
138 }
139}
140pub struct Worker {
141 child: Child,
142 input: ChildStdin,
143 output: ChildStdout,
144 _guard: ProcessGuard,
145 diagnostic: tokio::task::JoinHandle<()>,
146 id: u64,
147}
148impl Worker {
149 pub fn pid(&self) -> Option<u32> {
150 self.child.id()
151 }
152 pub async fn kill(&mut self) {
153 let _ = self.child.kill().await;
154 let _ = self.child.wait().await;
155 }
156 pub async fn call(
157 &mut self,
158 method: &str,
159 params: Value,
160 seconds: f64,
161 token: &CancellationToken,
162 ) -> Result<(Value, Vec<u8>)> {
163 let result = tokio::select! {
164 biased;
165 _=token.cancelled()=>Err(Failure::Cancelled.into()),
166 result=tokio::time::timeout(Duration::from_secs_f64(seconds),self.exchange(method,params))=>result.unwrap_or_else(|_|Err(anyhow::anyhow!("Worker {method} timed out after {seconds} seconds"))),
167 };
168 if result.is_err()
169 && !matches!(
170 result
171 .as_ref()
172 .err()
173 .and_then(|e| e.downcast_ref::<Failure>()),
174 Some(Failure::Worker { .. })
175 )
176 {
177 self.kill().await;
178 }
179 result
180 }
181 async fn exchange(&mut self, method: &str, params: Value) -> Result<(Value, Vec<u8>)> {
182 self.id += 1;
183 let bytes =
184 serde_json::to_vec(&json!({"version":1,"id":self.id,"method":method,"params":params}))?;
185 ensure!(
186 bytes.len() <= 65536,
187 Failure::Invalid("Command too large".into())
188 );
189 self.input.write_u32_le(bytes.len() as u32).await?;
190 self.input.write_all(&bytes).await?;
191 self.input.flush().await?;
192 let length = self.output.read_u32_le().await? as usize;
193 ensure!(
194 (1..=65536).contains(&length),
195 Failure::Invalid("Invalid worker response length".into())
196 );
197 let mut header = vec![0; length];
198 self.output.read_exact(&mut header).await?;
199 let reply: Value =
200 serde_json::from_slice(&header).map_err(|_| invalid("Malformed worker JSON"))?;
201 ensure!(
202 reply["id"] == self.id && reply["version"] == 1,
203 Failure::Invalid("Stale worker response".into())
204 );
205 let count = reply["binaryLength"]
206 .as_u64()
207 .ok_or_else(|| invalid("Missing frame length"))?;
208 ensure!(
209 count <= 512 * 1024 * 1024 && (method == "download" || count == 0),
210 Failure::Invalid("Invalid worker image length".into())
211 );
212 if reply["ok"] == false {
213 ensure!(
214 count == 0,
215 Failure::Invalid("Error response contains pixels".into())
216 );
217 return Err(Failure::Worker {
218 message: reply["error"].as_str().unwrap_or("Worker error").into(),
219 code: reply["sdkCode"].as_i64().map(|v| v as i32),
220 }
221 .into());
222 }
223 ensure!(
224 reply["ok"] == true,
225 Failure::Invalid("Missing worker status".into())
226 );
227 let mut pixels = vec![0; count as usize];
228 self.output.read_exact(&mut pixels).await?;
229 Ok((reply["result"].clone(), pixels))
230 }
231}
232impl Drop for Worker {
233 fn drop(&mut self) {
234 self.diagnostic.abort();
235 }
236}