Skip to main content

care_effectors/
lib.rs

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        // Handshake
30        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        // Dispatch commands to appropriate trait functions
40        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}