From f3c6518075a59c0dc9c3851da08e670e4ba0e471 Mon Sep 17 00:00:00 2001
From: stuebinm
Date: Tue, 30 Jan 2024 01:41:58 +0100
Subject: some initial hacking

most things work, but xochitl wants more meta data before things'll work
---
 src/main.rs | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 262 insertions(+)
 create mode 100644 src/main.rs

(limited to 'src')

diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..b500951
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,262 @@
+use std::{io::{Stdin, IsTerminal}, process::{exit, Command, Stdio}};
+
+
+struct Config {
+    ssh_destination: String,
+    xochitl_path: String,
+    ssh_key_path: Option<String>,
+    extensions: Extensions
+}
+
+#[derive(Default, Clone)]
+struct Extensions {
+    unavailable_response: bool,
+    info: bool
+}
+
+fn get_line(stdin: &mut Stdin) -> Option<String> {
+    let mut buf = String::new();
+    match stdin.read_line(&mut buf) {
+        Ok(0) => None,
+        Err(err) => {
+            eprintln!("read encountered error: {}", err);
+            None
+        }
+        _ => Some(buf)
+    }
+}
+
+fn get_value(stdin: &mut Stdin) -> Option<String> {
+    if let Some(line) = get_line(stdin) {
+        if let Some(value) = line.strip_prefix("VALUE ") {
+            if value.split_ascii_whitespace().count() == 0 {
+                return None
+            } else {
+                return Some(value.to_owned());
+            }
+        }
+        if line == "VALUE" {
+            return None;
+        }
+    }
+    eprintln!("did not receive value when expected");
+    exit(1);
+}
+
+fn get_config_value(stdin: &mut Stdin, key: &str) -> Option<String> {
+    println!("GETCONFIG {}", key);
+    get_value(stdin)
+}
+
+fn get_config(stdin: &mut Stdin, extensions: &Extensions) -> Config {
+    Config {
+        ssh_destination: get_config_value(stdin, "ssh_destination").unwrap()
+            .strip_suffix("\n").unwrap().to_string(),
+        xochitl_path: get_config_value(stdin, "xochitl_directory")
+            .map(|s| s.strip_suffix("\n").unwrap().to_string())
+            .unwrap_or_else(|| ".local/share/remarkable/xochitl".to_owned()),
+        ssh_key_path: get_config_value(stdin, "ssh_key"),
+        extensions: extensions.to_owned()
+    }
+}
+
+fn startup(stdin: &mut Stdin) -> Config {
+    let mut extensions = Extensions::default();
+    while let Some(line) = get_line(stdin) {
+        let mut words = line.split_ascii_whitespace();
+        let verb = words.next().expect("missing command verb");
+        match verb {
+            "EXTENSIONS" => {
+                let supported: Vec<_> = words.collect();
+                extensions.unavailable_response = supported.contains(&"UNAVAILABLERESPONSE");
+                extensions.info = supported.contains(&"INFO");
+                println!(
+                    "EXTENSIONS {} {}",
+                    if extensions.unavailable_response { "UNAVAILABLERESPONSE" } else { "" },
+                    if extensions.info { "INFO" } else { "" }
+                )
+            }
+            "PREPARE" => {
+                let config = get_config(stdin, &extensions);
+                println!("PREPARE-SUCCESS");
+                return config;
+            },
+            "LISTCONFIGS" => {
+                println!("CONFIG ssh_destination ssh destination under which the reMarkable tablet can be reached.");
+                println!("CONFIG ssh_key the path to an ssh key to use (optional).");
+                println!("CONFIG xochitl_directory the directory on the remarkable device in which data is stored (default .local/share/remarkable/xochitl).");
+                println!("CONFIGEND")
+            },
+            "INITREMOTE" => {
+                let config = get_config(stdin, &extensions);
+                let check = Command::new("ssh")
+                    .arg(config.ssh_destination)
+                    .arg("ls")
+                    .arg(config.xochitl_path)
+                    .stdout(Stdio::null())
+                    .status();
+                if let Ok(status) = check {
+                    if status.success() {
+                        println!("INITREMOTE-SUCCESS");
+                    } else {
+                        println!("INITREMOTE-FAILURE ssh check returned {}", status);
+                    }
+                } else {
+                    println!("INITREMOTE-FAILURE failed to spawn ssh check command");
+                }
+            }
+            _ => {
+                println!("UNSUPPORTED-REQUEST");
+                eprintln!("got unsupported verb while getting config: {}", line);
+            }
+        }
+    }
+    println!("DEBUG done early?");
+    exit(0);
+}
+
+
+fn key_to_uuid(key: &str) -> String {
+    let maybe = Command::new("uuidgen")
+        .arg("-s")
+        .arg("-n")
+        .arg("435bb27b-6704-416f-bc5b-3e2d02d0cf8f")
+        .arg("-N")
+        .arg(key)
+        .output();
+    match maybe {
+        Ok(uuid) => String::from_utf8_lossy(&uuid.stdout)
+            .split_ascii_whitespace().next().unwrap().to_owned(),
+        Err(e) => {
+            println!("DEBUG conversion to uuid failed: {}", e);
+            exit(1)
+        }
+    }
+}
+
+fn check_key_present<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
+    let key = words.next().unwrap();
+    assert_no_args_left(words);
+    let uuid = key_to_uuid(key);
+    let check = Command::new("ssh")
+        .arg(&config.ssh_destination)
+        .arg(format!("test -f {dir}/{uuid}.pdf && test ! -f {dir}/{uuid}.tombstone", dir=config.xochitl_path, uuid=uuid))
+        .status();
+    if let Ok(code) = check {
+        if code.success() {
+            println!("CHECKPRESENT-SUCCESS {}", key);
+        } else {
+            println!("CHECKPRESENT-FAILURE {}", key); // TODO: check if remote present or not
+        }
+    } else {
+        println!("CHECKPRESENT-UNKNOWN {} ssh failed", key);
+    }
+}
+
+fn store_key<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
+    let key = words.next().unwrap();
+    let file = words.next().unwrap();
+    assert_no_args_left(words);
+    let uuid = key_to_uuid(key);
+    let check = Command::new("scp")
+        .arg(file)
+        .arg(format!("{}:{}/{}.pdf", config.ssh_destination, config.xochitl_path, uuid))
+        .status();
+    if let Ok(code) = check {
+        if code.success() {
+            println!("TRANSFER-SUCCESS STORE {}", key);
+        } else {
+            println!("TRANSFER-FAILURE STORE {}", key); // TODO: check if remote present or not
+        }
+    } else {
+        println!("TRANSFER-FAILURE STORE {} ssh failed", key);
+    }
+}
+
+fn retrieve_key<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
+    let key = words.next().unwrap();
+    let file = words.next().unwrap();
+    assert_no_args_left(words);
+    let uuid = key_to_uuid(key);
+    let check = Command::new("scp")
+        .arg(format!("{}:{}/{}.pdf", config.ssh_destination, config.xochitl_path, uuid))
+        .arg(file)
+        .status();
+    if let Ok(code) = check {
+        if code.success() {
+            println!("TRANSFER-SUCCESS RETRIEVE {}", key);
+        } else {
+            println!("TRANSFER-FAILURE RETRIEVE {}", key); // TODO: check if remote present or not
+        }
+    } else {
+        println!("TRANSFER-FAILURE RETRIEVE {} ssh failed", key);
+    }
+}
+
+fn remove_key<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
+    let key = words.next().unwrap();
+    assert_no_args_left(words);
+    let uuid = key_to_uuid(key);
+
+    let cmd = Command::new("ssh")
+        .arg(&config.ssh_destination)
+        .arg(format!("rm -r {dir}/{uuid}*", dir=config.xochitl_path, uuid=uuid))
+        .status();
+    if let Ok(code) = cmd {
+        if code.success() {
+            println!("REMOVE-SUCCESS {}", key);
+        } else {
+            println!("REMOVE-FAILURE {} command failed", key); // TODO: check if remote present or not
+        }
+    } else {
+        println!("REMOVE-FAILURE {} ssh failed", key);
+    }
+}
+
+fn whereis<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
+    let key = words.next().unwrap();
+    assert_no_args_left(words);
+    let uuid = key_to_uuid(key);
+    println!("WHEREIS-SUCCESS {ssh}:{dir}/{uuid}.pdf", ssh=config.ssh_destination, dir=config.xochitl_path, uuid=uuid);
+}
+
+fn assert_no_args_left<'a>(mut words: impl Iterator<Item=&'a str>) {
+    if words.next().is_some() {
+        println!("UNSUPPORTED-REQUEST");
+        println!("ERROR");
+        exit(1);
+    }
+}
+
+fn main() {
+    let mut stdin = std::io::stdin();
+    if stdin.is_terminal() {
+        eprintln!("warning: this program is not meant to be invoked by hand.");
+    }
+
+    println!("VERSION 2");
+
+    let config = startup(&mut stdin);
+
+    while let Some(line) = get_line(&mut stdin) {
+        let mut words = line.split_ascii_whitespace();
+        let verb = words.next().expect("missing command verb");
+        match verb {
+            "CHECKPRESENT" => check_key_present(&config, words),
+            "TRANSFER" => match words.next().expect("missing STORE/RETRIEVE") {
+                "STORE" => store_key(&config, words),
+                "RETRIEVE" => retrieve_key(&config, words),
+                _ => println!("UNSUPPORTED-REQUEST")
+            }
+            "REMOVE" => remove_key(&config, words),
+            "WHEREIS" => whereis(&config, words),
+            "GETAVAILABILITY" => {
+                println!("ERROR");
+                unimplemented!()
+                // check routing table if usb network device is present?
+            }
+            _ => println!("UNSUPPORTED-REQUEST")
+        }
+    }
+
+}
-- 
cgit v1.2.3