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 let builder = builder.mode(Mode::Rpc);
33 if let Some(dir) = builder.get_working_directory() {
34 cmd.current_dir(dir);
35 }
36 cmd.args(builder.assembled_args())
37 .stdin(std::process::Stdio::piped())
38 .stdout(std::process::Stdio::piped())
39 .stderr(std::process::Stdio::piped())
40 .kill_on_drop(true);
41 let mut child = cmd.spawn()?;
42 let stdin = child.stdin.take().expect("piped stdin");
43 let stdout = child.stdout.take().expect("piped stdout");
44 Ok(Self {
45 child,
46 stdin,
47 stdout: BufReader::new(stdout).lines(),
48 pending_events: VecDeque::new(),
49 next_id: 0,
50 })
51 }
52
53 pub async fn start() -> Result<Self> {
55 Self::spawn(PiCliBuilder::new().no_session(true)).await
56 }
57
58 fn fresh_id(&mut self) -> String {
59 self.next_id += 1;
60 format!("pi-codes-{}", self.next_id)
61 }
62
63 pub async fn request(&mut self, mut command: RpcCommand) -> Result<RpcResponse> {
69 let id = match command_id(&command) {
70 Some(id) => id,
71 None => {
72 let id = self.fresh_id();
73 set_command_id(&mut command, &id);
74 id
75 }
76 };
77 let line = command.to_line()?;
78 self.stdin.write_all(line.as_bytes()).await?;
79 self.stdin.flush().await?;
80
81 loop {
82 let v = self.next_value().await?;
83 if v.get("type").and_then(serde_json::Value::as_str) == Some("response") {
84 let resp: RpcResponse =
85 serde_json::from_value(v.clone()).map_err(|source| Error::Deserialization {
86 source,
87 raw_line: v.to_string(),
88 })?;
89 match &resp.id {
93 Some(rid) if rid != &id => continue,
94 _ => return Ok(resp),
95 }
96 }
97 self.pending_events
98 .push_back(PiEvent::from_value(v.clone()).map_err(|source| {
99 Error::Deserialization {
100 source,
101 raw_line: v.to_string(),
102 }
103 })?);
104 }
105 }
106
107 pub async fn request_ok(&mut self, command: RpcCommand) -> Result<RpcResponse> {
109 let resp = self.request(command).await?;
110 if resp.success {
111 Ok(resp)
112 } else {
113 Err(Error::CommandFailed(
114 resp.error.unwrap_or_else(|| resp.command.clone()),
115 ))
116 }
117 }
118
119 pub async fn next_event(&mut self) -> Result<Option<PiEvent>> {
122 if let Some(e) = self.pending_events.pop_front() {
123 return Ok(Some(e));
124 }
125 match self.try_next_value().await? {
126 None => Ok(None),
127 Some(v) => Ok(Some(PiEvent::from_value(v.clone()).map_err(|source| {
128 Error::Deserialization {
129 source,
130 raw_line: v.to_string(),
131 }
132 })?)),
133 }
134 }
135
136 async fn next_value(&mut self) -> Result<serde_json::Value> {
137 match self.try_next_value().await? {
138 Some(v) => Ok(v),
139 None => {
140 let mut err = String::new();
141 if let Some(stderr) = self.child.stderr.take() {
142 use tokio::io::AsyncReadExt;
143 let mut r = BufReader::new(stderr);
144 let _ = r.read_to_string(&mut err).await;
145 }
146 Err(Error::ProcessExited(err.trim().to_string()))
147 }
148 }
149 }
150
151 async fn try_next_value(&mut self) -> Result<Option<serde_json::Value>> {
152 loop {
153 let Some(line) = self.stdout.next_line().await? else {
154 return Ok(None);
155 };
156 let line = line.trim_end_matches('\r');
157 if line.trim().is_empty() {
158 continue;
159 }
160 return serde_json::from_str(line)
161 .map(Some)
162 .map_err(|source| Error::Deserialization {
163 source,
164 raw_line: line.to_string(),
165 });
166 }
167 }
168
169 pub async fn shutdown(mut self) -> Result<()> {
171 let _ = self.child.kill().await;
172 let _ = self.child.wait().await;
173 Ok(())
174 }
175}
176
177fn command_id(c: &RpcCommand) -> Option<String> {
178 match serde_json::to_value(c) {
179 Ok(v) => v
180 .get("id")
181 .and_then(serde_json::Value::as_str)
182 .map(str::to_string),
183 Err(_) => None,
184 }
185}
186
187fn set_command_id(c: &mut RpcCommand, id: &str) {
188 if let Ok(mut v) = serde_json::to_value(&*c) {
191 if let Some(obj) = v.as_object_mut() {
192 obj.insert("id".into(), serde_json::Value::String(id.to_string()));
193 if let Ok(with_id) = serde_json::from_value::<RpcCommand>(v) {
194 *c = with_id;
195 }
196 }
197 }
198}