1use crate::cli::{Mode, PiCliBuilder};
9use crate::error::{Error, Result};
10use crate::io::PiEvent;
11use crate::rpc::{RpcCommand, RpcResponse};
12use std::collections::VecDeque;
13use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
14use tokio::process::{Child, ChildStdin, ChildStdout};
15
16pub struct PiRpcClient {
18 child: Child,
19 stdin: ChildStdin,
20 stdout: tokio::io::Lines<BufReader<ChildStdout>>,
21 pending_events: VecDeque<PiEvent>,
23 next_id: u64,
24}
25
26impl PiRpcClient {
27 pub async fn spawn(builder: PiCliBuilder) -> Result<Self> {
30 let program = which::which("pi").map_err(|_| Error::BinaryNotFound)?;
31 let mut cmd = tokio::process::Command::new(program);
32 cmd.args(builder.mode(Mode::Rpc).assembled_args())
33 .stdin(std::process::Stdio::piped())
34 .stdout(std::process::Stdio::piped())
35 .stderr(std::process::Stdio::piped())
36 .kill_on_drop(true);
37 let mut child = cmd.spawn()?;
38 let stdin = child.stdin.take().expect("piped stdin");
39 let stdout = child.stdout.take().expect("piped stdout");
40 Ok(Self {
41 child,
42 stdin,
43 stdout: BufReader::new(stdout).lines(),
44 pending_events: VecDeque::new(),
45 next_id: 0,
46 })
47 }
48
49 pub async fn start() -> Result<Self> {
51 Self::spawn(PiCliBuilder::new().no_session(true)).await
52 }
53
54 fn fresh_id(&mut self) -> String {
55 self.next_id += 1;
56 format!("pi-codes-{}", self.next_id)
57 }
58
59 pub async fn request(&mut self, mut command: RpcCommand) -> Result<RpcResponse> {
65 let id = match command_id(&command) {
66 Some(id) => id,
67 None => {
68 let id = self.fresh_id();
69 set_command_id(&mut command, &id);
70 id
71 }
72 };
73 let line = command.to_line()?;
74 self.stdin.write_all(line.as_bytes()).await?;
75 self.stdin.flush().await?;
76
77 loop {
78 let v = self.next_value().await?;
79 if v.get("type").and_then(serde_json::Value::as_str) == Some("response") {
80 let resp: RpcResponse =
81 serde_json::from_value(v.clone()).map_err(|source| Error::Deserialization {
82 source,
83 raw_line: v.to_string(),
84 })?;
85 match &resp.id {
89 Some(rid) if rid != &id => continue,
90 _ => return Ok(resp),
91 }
92 }
93 self.pending_events
94 .push_back(PiEvent::from_value(v.clone()).map_err(|source| {
95 Error::Deserialization {
96 source,
97 raw_line: v.to_string(),
98 }
99 })?);
100 }
101 }
102
103 pub async fn request_ok(&mut self, command: RpcCommand) -> Result<RpcResponse> {
105 let resp = self.request(command).await?;
106 if resp.success {
107 Ok(resp)
108 } else {
109 Err(Error::CommandFailed(
110 resp.error.unwrap_or_else(|| resp.command.clone()),
111 ))
112 }
113 }
114
115 pub async fn next_event(&mut self) -> Result<Option<PiEvent>> {
118 if let Some(e) = self.pending_events.pop_front() {
119 return Ok(Some(e));
120 }
121 match self.try_next_value().await? {
122 None => Ok(None),
123 Some(v) => Ok(Some(PiEvent::from_value(v.clone()).map_err(|source| {
124 Error::Deserialization {
125 source,
126 raw_line: v.to_string(),
127 }
128 })?)),
129 }
130 }
131
132 async fn next_value(&mut self) -> Result<serde_json::Value> {
133 match self.try_next_value().await? {
134 Some(v) => Ok(v),
135 None => {
136 let mut err = String::new();
137 if let Some(stderr) = self.child.stderr.take() {
138 use tokio::io::AsyncReadExt;
139 let mut r = BufReader::new(stderr);
140 let _ = r.read_to_string(&mut err).await;
141 }
142 Err(Error::ProcessExited(err.trim().to_string()))
143 }
144 }
145 }
146
147 async fn try_next_value(&mut self) -> Result<Option<serde_json::Value>> {
148 loop {
149 let Some(line) = self.stdout.next_line().await? else {
150 return Ok(None);
151 };
152 let line = line.trim_end_matches('\r');
153 if line.trim().is_empty() {
154 continue;
155 }
156 return serde_json::from_str(line)
157 .map(Some)
158 .map_err(|source| Error::Deserialization {
159 source,
160 raw_line: line.to_string(),
161 });
162 }
163 }
164
165 pub async fn shutdown(mut self) -> Result<()> {
167 let _ = self.child.kill().await;
168 let _ = self.child.wait().await;
169 Ok(())
170 }
171}
172
173fn command_id(c: &RpcCommand) -> Option<String> {
174 match serde_json::to_value(c) {
175 Ok(v) => v
176 .get("id")
177 .and_then(serde_json::Value::as_str)
178 .map(str::to_string),
179 Err(_) => None,
180 }
181}
182
183fn set_command_id(c: &mut RpcCommand, id: &str) {
184 if let Ok(mut v) = serde_json::to_value(&*c) {
187 if let Some(obj) = v.as_object_mut() {
188 obj.insert("id".into(), serde_json::Value::String(id.to_string()));
189 if let Ok(with_id) = serde_json::from_value::<RpcCommand>(v) {
190 *c = with_id;
191 }
192 }
193 }
194}