summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuebinm2024-03-07 20:59:58 +0100
committerstuebinm2024-03-07 20:59:58 +0100
commit6befe9f334a3384e6a6bf494211edb4e549efa2f (patch)
treefb0ee621f5146f4bed3c0cf42d59134b3c366431
parentf3c6518075a59c0dc9c3851da08e670e4ba0e471 (diff)
set up xochitl file structure, then scpHEADmain
this results in a semi-workable thing. the biggest problem is that special remotes aren't really supposed to know much (or anything) about the files they store, which is an assumption I break in the most direct way possible: it only makes sense to store pdfs on a reMarkable. So this now uses exiftool to get the pdf's title. Unfortunately, most of my pdfs turn out to not have any titles – and those that have often have useless titles set (e.g. "Microsoft Word Document" or some such). Will have to think about that one a bit ..
-rw-r--r--src/main.rs168
1 files changed, 152 insertions, 16 deletions
diff --git a/src/main.rs b/src/main.rs
index b500951..8085ec6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,13 @@
-use std::{io::{Stdin, IsTerminal}, process::{exit, Command, Stdio}};
+use std::{io::{Stdin, IsTerminal, Write}, process::{exit, Command, Stdio}, fs::File};
struct Config {
ssh_destination: String,
xochitl_path: String,
ssh_key_path: Option<String>,
- extensions: Extensions
+ extensions: Extensions,
+ tmpdir: String,
+ uuid: String
}
#[derive(Default, Clone)]
@@ -39,24 +41,32 @@ fn get_value(stdin: &mut Stdin) -> Option<String> {
return None;
}
}
- eprintln!("did not receive value when expected");
+ println!("ERROR did not receive value when expected");
exit(1);
}
+fn get_uuid(stdin: &mut Stdin) -> Option<String> {
+ println!("GETUUID");
+ get_value(stdin)
+ .map(|s| s.strip_suffix("\n").unwrap().to_string())
+}
+
fn get_config_value(stdin: &mut Stdin, key: &str) -> Option<String> {
println!("GETCONFIG {}", key);
get_value(stdin)
+ .map(|s| s.strip_suffix("\n").unwrap().to_string())
}
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(),
+ ssh_destination: get_config_value(stdin, "ssh_destination").unwrap(),
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()
+ tmpdir: get_config_value(stdin, "tmpdir")
+ .unwrap_or_else(|| "/tmp/xochitl".to_owned()),
+ extensions: extensions.to_owned(),
+ uuid: get_uuid(stdin).unwrap()
}
}
@@ -116,11 +126,11 @@ fn startup(stdin: &mut Stdin) -> Config {
}
-fn key_to_uuid(key: &str) -> String {
+fn key_to_uuid(config: &Config, key: &str) -> String {
let maybe = Command::new("uuidgen")
.arg("-s")
.arg("-n")
- .arg("435bb27b-6704-416f-bc5b-3e2d02d0cf8f")
+ .arg(&config.uuid)
.arg("-N")
.arg(key)
.output();
@@ -134,10 +144,33 @@ fn key_to_uuid(key: &str) -> String {
}
}
+fn get_exifdata(filepath: &str, key: &str) -> Option<String> {
+ let maybe = Command::new("exiftool")
+ .arg(filepath)
+ .arg("-b")
+ .arg(format!("-{}", key))
+ .output();
+ match maybe {
+ Ok(out) => {
+ let value = String::from_utf8_lossy(&out.stdout);
+ if value.split_ascii_whitespace().next().is_some() {
+ Some(value.to_string())
+ } else {
+ println!("DEBUG could not get exif data for key {}: empty value returned", key);
+ return None
+ }
+ },
+ Err(e) => {
+ println!("DEBUG could not get exif data for key {}: {}", key, e);
+ return None
+ }
+ }
+}
+
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 uuid = key_to_uuid(config, 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))
@@ -153,14 +186,104 @@ fn check_key_present<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>
}
}
+// see https://www.ietf.org/rfc/rfc4627.txt
+fn json_string_escape(str: &str) -> String {
+ let mut buf = String::with_capacity(str.len());
+ for c in str.chars() {
+ match c {
+ '"' => buf.push_str("\\\""),
+ '\\' => buf.push_str("\\\\"),
+ // technically (maybe?) overshoots? the rfc seems to think only 0x00
+ // to 0x1f are control characters
+ _ if c.is_control() => buf.push_str(&format!("\\u{:04x}", c as u32)),
+ _ => buf.push(c)
+ }
+ }
+ buf
+}
+
+#[test]
+fn test_json() {
+ assert_eq!(json_string_escape("abcd"), "abcd");
+ assert_eq!(json_string_escape("ab\"d"), "ab\\\"d");
+ assert_eq!(json_string_escape("ab\nd"), "ab\\u000ad");
+}
+
+fn setup_filestructure(config: &Config, path: &str, uuid: &str, file: &str) -> Option<()> {
+ let title = match get_exifdata(file, "Title") {
+ Some(title) => title,
+ None => {
+ println!("DEBUG could not get title from exif data, using uuid instead");
+ uuid.to_owned()
+ // TODO: this can't easily access the actual file name, since the file we
+ // get is actually the version in git annex's store, which is content-addressed,
+ // and special remotes aren't really supposed to know anything about the file
+ // they store.
+ //
+ // As a workaround, maybe iterate through the repo's file listing until a
+ // matching symlink is found, than use that one's file name?
+ }
+ };
+ if config.extensions.info {
+ println!("INFO using title {:?}", title);
+ }
+ Command::new("mkdir")
+ .arg("-p")
+ .arg(&path)
+ .status().ok()?;
+ Command::new("ln")
+ .arg("-s")
+ .arg("-r")
+ .arg(file)
+ .arg(format!("{}/{}.pdf", path, uuid))
+ .status().ok()?;
+ let mut metadata = File::create(format!("{}/{}.metadata", path, uuid)).ok()?;
+ metadata.write_all(format!(r#"{{
+ "createdTime": "{time}",
+ "lastModified": "{time}",
+ "lastOpened": "0",
+ "lastOpenedPage": 0,
+ "parent": "",
+ "pinned": false,
+ "type": "DocumentType",
+ "visibleName": "{title}"
+ }}"#, time=10, title=json_string_escape(&title)).as_bytes()).ok()?;
+ let mut content = File::create(format!("{}/{}.content", path, uuid)).ok()?;
+ content.write_all(format!(r#"{{
+ "coverPageNumber": 0,
+ "documentMetadata": {{}},
+ "extraMetadata": {{}},
+ "fileType": "pdf",
+ "fontName": "",
+ "pageTags": [],
+ "tags": [],
+ "textAlignment": "justify",
+ "textScale": 1,
+ "zoomMode": "bestFit"
+ }}"#).as_bytes()).ok()?;
+ Some(())
+}
+
+
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 uuid = key_to_uuid(config, key);
+ let path = format!("{}/{}", config.tmpdir, uuid);
+
+ if setup_filestructure(config, &path, &uuid, file).is_none() {
+ println!("TRANSFER-FAILURE STORE {} could not set up xochitl file structure", key);
+ return
+ }
+
+ // TODO: check if anything with that uuid is present already, otherwise destructive
let check = Command::new("scp")
- .arg(file)
- .arg(format!("{}:{}/{}.pdf", config.ssh_destination, config.xochitl_path, uuid))
+ .arg("-r")
+ .arg(format!("{}/{}.pdf", path, uuid))
+ .arg(format!("{}/{}.content", path, uuid))
+ .arg(format!("{}/{}.metadata", path, uuid))
+ .arg(format!("{}:{}", config.ssh_destination, config.xochitl_path))
.status();
if let Ok(code) = check {
if code.success() {
@@ -171,13 +294,26 @@ fn store_key<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
} else {
println!("TRANSFER-FAILURE STORE {} ssh failed", key);
}
+
+ let cleanup = Command::new("rm")
+ .arg("-r")
+ .arg(&path)
+ .status();
+ let maybe_info = if config.extensions.info { "INFO" } else { "DEBUG" };
+ if let Ok(code) = cleanup {
+ if !code.success() {
+ println!("{} could not clean up tmp path {}: `rm' returned code {}", maybe_info, path, code);
+ }
+ } else {
+ println!("{} could not clean up tmp path {}: could not run `rm'", maybe_info, path);
+ }
}
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 uuid = key_to_uuid(config, key);
let check = Command::new("scp")
.arg(format!("{}:{}/{}.pdf", config.ssh_destination, config.xochitl_path, uuid))
.arg(file)
@@ -196,7 +332,7 @@ fn retrieve_key<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
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 uuid = key_to_uuid(config, key);
let cmd = Command::new("ssh")
.arg(&config.ssh_destination)
@@ -216,7 +352,7 @@ fn remove_key<'a>(config: &Config, mut words: impl Iterator<Item=&'a str>) {
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);
+ let uuid = key_to_uuid(config, key);
println!("WHEREIS-SUCCESS {ssh}:{dir}/{uuid}.pdf", ssh=config.ssh_destination, dir=config.xochitl_path, uuid=uuid);
}