summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorstuebinm2024-01-30 01:41:58 +0100
committerstuebinm2024-01-30 01:41:58 +0100
commitf3c6518075a59c0dc9c3851da08e670e4ba0e471 (patch)
tree25dc9ad24219782113b23ca70698eed1218d6596 /src
some initial hacking
most things work, but xochitl wants more meta data before things'll work
Diffstat (limited to '')
-rw-r--r--src/main.rs262
1 files changed, 262 insertions, 0 deletions
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")
+ }
+ }
+
+}