1#![warn(
3 clippy::complexity,
4 clippy::correctness,
5 clippy::style,
6 future_incompatible,
7 missing_debug_implementations,
8 missing_docs,
9 rust_2018_idioms,
10 rustdoc::all,
11 clippy::undocumented_unsafe_blocks
12)]
13use ninep::{
14 sansio::server::socket_dir,
15 sync::client::{ReadLineIter, UnixClient},
16};
17use std::{env, fs, io, io::Write, os::unix::net::UnixStream, str::FromStr};
18
19mod event;
20
21pub use ad_event::Source;
22pub use event::{EventFilter, Outcome};
23
24#[derive(Debug, Clone)]
26pub struct Client {
27 inner: UnixClient,
28 ns: String,
29}
30
31impl Client {
32 pub fn new() -> io::Result<Self> {
34 let ns = match env::var("AD_PID") {
35 Ok(pid) => format!("ad-{pid}"),
36 Err(_) => "ad".to_string(),
37 };
38
39 Ok(Self {
40 inner: UnixClient::new_unix(&ns, "")?,
41 ns,
42 })
43 }
44
45 pub fn new_for_pid(pid: &str) -> io::Result<Self> {
51 let ns = format!("ad-{pid}");
52
53 Ok(Self {
54 inner: UnixClient::new_unix(&ns, "")?,
55 ns,
56 })
57 }
58
59 pub(crate) fn event_lines(&mut self, buffer: &str) -> io::Result<ReadLineIter<UnixStream>> {
60 self.inner.iter_lines(format!("buffers/{buffer}/event"))
61 }
62
63 pub(crate) fn write_event(&mut self, buffer: &str, event_line: &str) -> io::Result<()> {
64 self.inner
65 .write_str(format!("buffers/{buffer}/event"), 0, event_line)?;
66 Ok(())
67 }
68
69 pub fn log_events(&mut self) -> io::Result<impl Iterator<Item = io::Result<LogEvent>> + use<>> {
71 Ok(self
72 .inner
73 .iter_lines("log")?
74 .map(|line| LogEvent::from_str(&line)))
75 }
76
77 pub fn current_buffer(&mut self) -> io::Result<String> {
79 self.inner.read_str("buffers/current")
80 }
81
82 fn _read_buffer_file(&mut self, buffer: &str, file: &str) -> io::Result<String> {
83 self.inner.read_str(format!("buffers/{buffer}/{file}"))
84 }
85
86 pub fn read_dot(&mut self, buffer: &str) -> io::Result<String> {
88 self._read_buffer_file(buffer, "dot")
89 }
90
91 pub fn read_body(&mut self, buffer: &str) -> io::Result<String> {
93 self._read_buffer_file(buffer, "body")
94 }
95
96 pub fn read_addr(&mut self, buffer: &str) -> io::Result<String> {
98 self._read_buffer_file(buffer, "addr")
99 }
100
101 pub fn read_filename(&mut self, buffer: &str) -> io::Result<String> {
103 self._read_buffer_file(buffer, "filename")
104 }
105
106 pub fn read_xaddr(&mut self, buffer: &str) -> io::Result<String> {
111 self._read_buffer_file(buffer, "xaddr")
112 }
113
114 pub fn read_xdot(&mut self, buffer: &str) -> io::Result<String> {
119 self._read_buffer_file(buffer, "xdot")
120 }
121
122 fn _write_buffer_file(
123 &mut self,
124 buffer: &str,
125 file: &str,
126 offset: u64,
127 content: &[u8],
128 ) -> io::Result<usize> {
129 self.inner
130 .write(format!("buffers/{buffer}/{file}"), offset, content)
131 }
132
133 pub fn write_dot(&mut self, buffer: &str, content: &str) -> io::Result<usize> {
135 self._write_buffer_file(buffer, "dot", 0, content.as_bytes())
136 }
137
138 pub fn append_to_body(&mut self, buffer: &str, content: &str) -> io::Result<usize> {
140 self._write_buffer_file(buffer, "body", 0, content.as_bytes())
141 }
142
143 pub fn write_addr(&mut self, buffer: &str, addr: &str) -> io::Result<usize> {
145 self._write_buffer_file(buffer, "addr", 0, addr.as_bytes())
146 }
147
148 pub fn write_xdot(&mut self, buffer: &str, content: &str) -> io::Result<usize> {
150 self._write_buffer_file(buffer, "xdot", 0, content.as_bytes())
151 }
152
153 pub fn write_xaddr(&mut self, buffer: &str, content: &str) -> io::Result<usize> {
155 self._write_buffer_file(buffer, "xaddr", 0, content.as_bytes())
156 }
157
158 pub fn ctl(&mut self, command: &str, args: &str) -> io::Result<()> {
160 self.inner
161 .write("ctl", 0, format!("{command} {args}").as_bytes())?;
162
163 Ok(())
164 }
165
166 pub fn echo(&mut self, msg: impl AsRef<str>) -> io::Result<()> {
168 self.ctl("echo", msg.as_ref())
169 }
170
171 pub fn open(&mut self, path: impl AsRef<str>) -> io::Result<()> {
173 self.ctl("open", path.as_ref())
174 }
175
176 pub fn open_in_new_window(&mut self, path: impl AsRef<str>) -> io::Result<()> {
178 self.ctl("open-in-new-window", path.as_ref())
179 }
180
181 pub fn reload_current_buffer(&mut self) -> io::Result<()> {
183 self.ctl("reload", "")
184 }
185
186 pub fn run_event_filter<F>(&mut self, buffer: &str, filter: F) -> io::Result<()>
188 where
189 F: EventFilter,
190 {
191 event::run_filter(buffer, filter, self)
192 }
193
194 pub fn body_writer(&self, bufid: &str) -> io::Result<BodyWriter> {
196 Ok(BodyWriter {
197 path: format!("buffers/{bufid}/body"),
198 client: UnixClient::new_unix(&self.ns, "")?,
199 })
200 }
201}
202
203#[derive(Debug)]
205pub struct BodyWriter {
206 path: String,
207 client: UnixClient,
208}
209
210impl BodyWriter {
211 pub fn mark_clean(&mut self) -> io::Result<()> {
213 self.client.write("ctl", 0, "mark-clean".as_bytes())?;
214
215 Ok(())
216 }
217}
218
219impl Write for BodyWriter {
220 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
221 self.client.write(&self.path, 0, buf)
222 }
223
224 fn flush(&mut self) -> io::Result<()> {
225 Ok(())
226 }
227}
228
229#[derive(Debug, Clone, Copy)]
232pub enum LogEvent {
233 Open(usize),
235 Close(usize),
237 Focus(usize),
239 Save(usize),
241}
242
243impl FromStr for LogEvent {
244 type Err = io::Error;
245
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
247 let s = s.trim();
248 if s.contains('\n') {
249 return Err(io::Error::new(
250 io::ErrorKind::InvalidData,
251 "expected single line",
252 ));
253 }
254
255 let (str_id, action) = s.split_once(' ').ok_or(io::Error::new(
256 io::ErrorKind::InvalidData,
257 "malformed log line: {s:?}",
258 ))?;
259
260 let id: usize = str_id.parse().map_err(|_| {
261 io::Error::new(
262 io::ErrorKind::InvalidData,
263 "expected integer ID, got {str_id:?}",
264 )
265 })?;
266
267 let evt = match action {
268 "open" => Self::Open(id),
269 "close" => Self::Close(id),
270 "focus" => Self::Focus(id),
271 "save" => Self::Save(id),
272 _ => {
273 return Err(io::Error::new(
274 io::ErrorKind::InvalidData,
275 "unknown log action {action:?}",
276 ));
277 }
278 };
279
280 Ok(evt)
281 }
282}
283
284fn open_9p_sockets() -> io::Result<Vec<String>> {
285 let mut ad_sockets = Vec::new();
286 for entry in fs::read_dir(socket_dir())? {
287 let entry = entry?;
288 let fname = entry.file_name();
289 if let Some(s) = fname.to_str()
290 && s.starts_with("ad-")
291 {
292 ad_sockets.push(s.to_string());
293 }
294 }
295
296 Ok(ad_sockets)
297}
298
299#[derive(Debug)]
301pub struct SessionMeta {
302 pub socket_name: String,
304 pub is_unresponsive: bool,
309 pub active_buffer_id: String,
311 pub buffers: Vec<BufferMeta>,
313}
314
315impl SessionMeta {
316 pub fn client_for_session(&self) -> io::Result<Client> {
318 Ok(Client {
319 inner: UnixClient::new_unix(&self.socket_name, "")?,
320 ns: self.socket_name.clone(),
321 })
322 }
323
324 pub fn remove_socket(&self) -> io::Result<()> {
326 fs::remove_file(socket_dir().join(&self.socket_name))
327 }
328}
329
330#[derive(Debug)]
332pub struct BufferMeta {
333 pub id: String,
335 pub filename: String,
337}
338
339pub fn remove_unresponsive_sessions() -> io::Result<()> {
341 for session in list_open_sessions()?.into_iter() {
342 if session.is_unresponsive {
343 session.remove_socket()?;
344 }
345 }
346
347 Ok(())
348}
349
350pub fn list_open_sessions() -> io::Result<Vec<SessionMeta>> {
352 let mut sessions = Vec::new();
353
354 for ns in open_9p_sockets()?.into_iter() {
355 let mut client = match UnixClient::new_unix(&ns, "") {
356 Ok(client) => client,
357 Err(_) => {
358 sessions.push(SessionMeta {
359 socket_name: ns,
360 is_unresponsive: true,
361 active_buffer_id: String::new(),
362 buffers: Vec::new(),
363 });
364 continue;
365 }
366 };
367 let active_buffer_id = client.read_str("buffers/current")?;
368 let buffers = client
369 .read_str("buffers/index")?
370 .lines()
371 .map(|line| {
372 let mut it = line.split_whitespace();
373 let id = it.next().map(String::from).unwrap_or_default();
374 let filename = it.next().map(String::from).unwrap_or_default();
375
376 BufferMeta { id, filename }
377 })
378 .collect();
379
380 sessions.push(SessionMeta {
381 socket_name: ns,
382 is_unresponsive: false,
383 active_buffer_id,
384 buffers,
385 });
386 }
387
388 Ok(sessions)
389}