1use anyhow::Result;
2
3use std::path::{Path, PathBuf};
4
5pub const HANDSHAKE_RQ: &str = "com.akavel.care.v2.rq";
6pub const HANDSHAKE_RS: &str = "com.akavel.care.v2.rs";
7
8pub trait Callee {
9 fn start(args: std::env::Args) -> Result<Self>
10 where
11 Self: Sized;
12
13 fn detect(&mut self, path: &Path) -> Result<bool>;
14 fn gather(&mut self, path: &Path, shadow_prefix: &Path) -> Result<()>;
15 fn affect(&mut self, path: &Path, shadow_prefix: &Path) -> Result<()>;
16
17 fn serve(args: std::env::Args) -> Result<()>
18 where
19 Self: Sized,
20 {
21 use anyhow::{anyhow, bail};
22 use itertools::Itertools;
23 use std::io::{BufRead, Write};
24
25 let mut c = Self::start(args)?;
26 let mut in_lines = std::io::stdin().lock().lines();
27 let mut out = std::io::stdout().lock();
28
29 let handshake = in_lines
31 .next()
32 .ok_or(anyhow!("expected handshake, got EOF on stdin"))??;
33 if !handshake.starts_with(HANDSHAKE_RQ) {
34 bail!("expected v2 handshake, got: {handshake:?}");
35 }
36 writeln!(out, "{}", HANDSHAKE_RS)?;
37 out.flush()?;
38
39 loop {
41 let Some(line) = in_lines.next().transpose()? else {
42 return Ok(());
43 };
44 let Some((cmd, args)) = line.split_once(' ') else {
45 bail!("expected command with args, got: {line:?}");
46 };
47 let mut args = args.split(' ').map(urldecode_to_path);
48 match cmd {
49 "detect" => {
50 let Some(path) = args.next() else {
51 bail!("expected 1 arg to 'detect', got none");
52 };
53 let res = c.detect(&path?)?;
54 writeln!(out, "detected {}", if res { "present" } else { "absent" })?;
55 }
56 "gather" => {
57 let Some((path1, path2)) = args.next_tuple() else {
58 bail!("expected 2 args to 'gather', got less");
59 };
60 c.gather(&path1?, &path2?)?;
61 writeln!(out, "gathered")?;
62 }
63 "affect" => {
64 let Some((path1, path2)) = args.next_tuple() else {
65 bail!("expected 2 args to 'affect', got less");
66 };
67 c.affect(&path1?, &path2?)?;
68 writeln!(out, "affected")?;
69 }
70 _ => bail!("unknown command: {cmd:?}"),
71 }
72 out.flush()?;
73 }
74 }
75}
76
77fn urldecode_to_path(s: &str) -> Result<PathBuf> {
78 use std::str::FromStr;
79 let decoded = urlencoding::decode(s)?;
80 let path = PathBuf::from_str(&decoded)?;
81 Ok(path)
82}