crabmail

Static HTML email archive viewer in Rust
git clone git://git.alexwennerberg.com/crabmail
Log | Files | Refs | README | LICENSE

commit 92f6e72a97e46f789165f9d718f0aba7cf88aec2
parent 223b252d35649ebea4683a857f510ee9defc1c4e
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Sat,  1 Jan 2022 09:56:06 -0800

Cleanup unuesd code / compiler warnings

Diffstat:
MCargo.lock | 7-------
MCargo.toml | 2--
Msrc/main.rs | 31++++---------------------------
Dsrc/mbox.rs | 104-------------------------------------------------------------------------------
Dsrc/threading.rs | 208-------------------------------------------------------------------------------
Msrc/utils.rs | 2+-
6 files changed, 5 insertions(+), 349 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -48,7 +48,6 @@ dependencies = [ "linkify", "maildir", "mailparse", - "nanotemplate", "once_cell", "sha3", "urlencoding", @@ -158,12 +157,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] -name = "nanotemplate" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcde141f0a9acabd38860369eeb0d69f1756d19c5948672c211e82e0519edd61" - -[[package]] name = "once_cell" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" [features] default = [] - [lib] proc-macro = true @@ -20,4 +19,3 @@ maildir = "0.5.0" once_cell = "1.9" sha3 = "0.10" urlencoding = "2.1" -nanotemplate = "0.2" diff --git a/src/main.rs b/src/main.rs @@ -4,7 +4,6 @@ use horrorshow::owned_html; use horrorshow::prelude::*; use horrorshow::Template; use maildir::Maildir; -use nanotemplate::template; // TODO write htmlescaper use std::io::BufWriter; use std::path::Path; use std::str; @@ -26,8 +25,6 @@ use config::{Config, INSTANCE}; use utils::xml_safe; mod arg; mod config; -mod mbox; -mod threading; mod time; mod utils; @@ -47,16 +44,6 @@ struct Email { date_string: String, body: String, mime: String, - attachments: Vec<Attachment>, -} - -#[derive(Debug, Clone)] -struct Attachment { - filename: String, -} - -impl Attachment { - fn local_path(&self) {} } #[derive(Debug, Clone)] @@ -187,7 +174,7 @@ impl<'a> ThreadList<'a> { : format!("{} Mailing List", &self.name); : Raw(" "); a(href="atom.xml") { - img(alt="Atom feed", src=utils::rss_svg); + img(alt="Atom feed", src=utils::RSS_SVG); } } @@ -297,7 +284,7 @@ impl<'a> MailThread<'a> { : &root.subject; : Raw(" "); a(href=format!("./{}.xml", self.hash)) { - img(alt="Atom feed", src=utils::rss_svg); + img(alt="Atom feed", src=utils::RSS_SVG); } } div { @@ -305,7 +292,7 @@ impl<'a> MailThread<'a> { : "Back"; } } div { - @ for (n, message) in self.messages.iter().enumerate() { + @ for message in self.messages.iter() { hr; div(id=&message.id, class="message") { span(class="bold") { @@ -413,13 +400,9 @@ fn parse_html_body(email: &ParsedMail) -> String { a } -fn parse_received() -> u64 { - 1 -} fn local_parse_email(parsed_mail: &ParsedMail) -> Result<Email> { let mut body: String = "[Message has no body]".to_owned(); let mut mime: String = "".to_owned(); - let attachments = vec![]; let nobody = "[No body found]"; // nested lookup let mut queue = vec![parsed_mail]; @@ -491,7 +474,6 @@ fn local_parse_email(parsed_mail: &ParsedMail) -> Result<Email> { date_string, body, mime, - attachments, }); } @@ -524,7 +506,6 @@ fn main() -> Result<()> { config.out_dir = args.out_dir; config.relative_times = args.flags.contains('r'); INSTANCE.set(config).unwrap(); - let out_dir = &Config::global().out_dir; // let is_subfolder = std::fs::read_dir(&args.maildir) // .unwrap() @@ -555,7 +536,7 @@ fn main() -> Result<()> { let mut thread_index: HashMap<String, Vec<String>> = HashMap::new(); let mut email_index: HashMap<String, Email> = HashMap::new(); - for mut entry in dirreader.list_cur().chain(dirreader.list_new()) { + for entry in dirreader.list_cur().chain(dirreader.list_new()) { let mut tmp = entry.unwrap(); let buffer = tmp.parsed()?; let email = match local_parse_email(&buffer) { @@ -705,7 +686,3 @@ fn get_current_threads(thread_dir: &Path) -> HashSet<String> { .filter(|x| !(x == "style")) .collect() } - -fn parse_path(s: &std::ffi::OsStr) -> Result<std::path::PathBuf, &'static str> { - Ok(s.into()) -} diff --git a/src/mbox.rs b/src/mbox.rs @@ -1,104 +0,0 @@ -// basically just rewrite of https://github.com/emersion/go-mbox/blob/master/reader.go -// so-called "mboxo" mbox format https://www.loc.gov/preservation/digital/formats/fdd/fdd000384.shtml -// (which has minor issues, described therein) -use anyhow::Result; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::Path; - -pub struct MboxReader<T> -where - T: BufRead, -{ - in_record: bool, - done: bool, - reader: T, -} - -// Impl iterator -// Next() -impl<T: BufRead> MboxReader<T> { - pub fn from_reader(reader: T) -> MboxReader<T> { - return MboxReader { - in_record: false, - done: false, - reader, - }; - } -} - -// ugly im bad at rust -pub fn from_file(p: &Path) -> Result<MboxReader<BufReader<File>>> { - let f = File::open(p)?; - let reader = BufReader::new(f); - let mboxr = MboxReader::from_reader(reader); - Ok(mboxr) -} -// not the prettiest but it works -impl<T: BufRead> Iterator for MboxReader<T> { - type Item = Result<Vec<u8>, std::io::Error>; - - fn next(&mut self) -> Option<Self::Item> { - // mbox is ASCII, so don't use String - if self.done { - return None; - } - let mut record = Vec::new(); - let mut current_line; - let mut res = 1; - while res != 0 { - current_line = vec![]; - res = match self.reader.read_until(b'\n', &mut current_line) { - Ok(l) => l, - Err(err) => return Some(Err(err)), - }; - - if current_line.starts_with(b"From ") { - if !self.in_record { - self.in_record = true; - continue; - } else { - break; - } - } - // MBOXO escaping - if current_line.starts_with(b">From") { - record.extend_from_slice(&current_line[1..]); - } else { - record.extend_from_slice(&current_line); - } - } - if res == 0 { - self.done = true; - } - if record.ends_with(b"\r\n\r\n") { - // Remove double CRLF - record.pop(); - record.pop(); - } - Some(Ok(record)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str; - - #[test] - fn test_simple() { - let data = - "From something\r\nmyemail\r\nmessage-id: 123\r\n\r\nFrom Another\r\n>From escape\r\n\r\n"; - let mut reader = BufReader::new(data.as_bytes()); - let mut mboxr = MboxReader::from_reader(reader); - assert_eq!( - str::from_utf8(&mboxr.next().unwrap().unwrap()).unwrap(), - "myemail\r\nmessage-id: 123\r\n" - ); - assert_eq!( - str::from_utf8(&mboxr.next().unwrap().unwrap()).unwrap(), - "From escape\r\n" - ); - assert!(mboxr.next().is_none()); - } -} diff --git a/src/threading.rs b/src/threading.rs @@ -1,208 +0,0 @@ -use anyhow::{anyhow, Context, Result}; -use mailparse::ParsedMail; -use mailparse::*; -use std::collections::HashMap; - -// https://rust-leipzig.github.io/architecture/2016/12/20/idiomatic-trees-in-rust/ -use maildir::MailEntry; - -#[derive(Default)] -pub struct Arena { - nodes: Vec<Container>, - container_index: HashMap<String, usize>, // Message ID, node index -} - -// Rust Implementation of https://www.jwz.org/doc/threading.html -// See "The Algorithm" -// WIP. this is hard -// TODO dont store the entire email here. we can read from disk -impl Arena { - pub fn add_message(&mut self, mut message: MailEntry) -> Result<()> { - let parsed = message.parsed()?; // escape now if invalid msg - let m_id = parsed - .headers - .get_first_value("message-id") - .and_then(|m| { - msgidparse(&m).ok().and_then(|i| match i.len() { - 0 => None, - _ => Some(i[0].clone()), - }) - }) - .context("No valid message ID")?; - let mut references = msgidparse( - &parsed - .headers - .get_first_value("references") - .unwrap_or("".to_owned()), - )?; - if let Some(irt) = msgidparse( - &parsed - .headers - .get_first_value("in_reply_to") - .unwrap_or("".to_string()), - )? - .iter() - .next() - { - references.push(irt.to_string()) - } - let this_id: usize; - match self.container_index.get(&m_id) { - Some(c) => { - this_id = *c; - match self.nodes[*c].data { - None => { - self.nodes[*c].data = Some(message); - } - Some(_) => panic!("Duplicate ID"), // TODO - } - } - None => { - let node = self.new_node(Some(message)); - self.container_index.insert(m_id.clone(), node); - this_id = node; - } - }; - for (n, reference) in references.iter().enumerate() { - match self.container_index.get(reference) { - Some(_) => {} - None => { - let new_node = self.new_node(None); - self.container_index.insert(reference.to_string(), new_node); - } - }; - // Link the References field's Containers together in the order implied by the - // References header. - if n > 1 && n != references.len() - 1 { - // TODO check loop - let i = self.container_index.get(&references[n - 1]).unwrap(); - self.set_child(*i, *self.container_index.get(reference).unwrap()); - } - } - // Set the parent of this message to be the last element in References - // remove existing reference - if let Some(par) = self.nodes[this_id].parent { - self.nodes[par].child = None; - } - self.nodes[this_id].parent = None; - - if references.len() > 1 { - //handle last element - let last_el = &references[references.len() - 1]; - let last_con = self.container_index.get(last_el).unwrap(); - self.set_child(*last_con, this_id); - } - - Ok(()) - } - pub fn finalize(mut self) { - let mut stack: &mut Vec<usize> = &mut self - .container_index - .iter() - .filter_map(|(k, v)| { - if self.nodes[*v].parent.is_none() { - return Some(*v); - } - None - }) - .collect(); - let root_set = stack.clone(); - while stack.len() > 0 { - let top = stack.pop().unwrap(); - let container = &self.nodes[top]; - if container.data.is_none() && container.child.is_some() { - self.set_child(container.parent.unwrap(), container.child.unwrap()); - } - } - - // group by subject - // let subject_table = HashMap::new(); - // for item in root_set { - // - } - - fn set_child(&mut self, parent: usize, child: usize) { - // TODO check non-recursive. do nothing. - let a = &mut self.nodes[parent]; - a.child = Some(child); - let b = &mut self.nodes[child]; - b.parent = Some(parent) - } - fn children_recursive() {} - fn new_node(&mut self, data: Option<MailEntry>) -> usize { - // Get the next free index - let next_index = self.nodes.len(); - - // Push the node into the arena - self.nodes.push(Container { - parent: None, - child: None, - next: None, - data: data, - }); - - // Return the node identifier - next_index - } -} - -pub struct Container { - parent: Option<usize>, - child: Option<usize>, - next: Option<usize>, - data: Option<MailEntry>, -} - -/// The actual data which will be stored within the tree -/// pub data: T, -/// } -/// -pub struct Message { - subject: String, - id: String, - references: MessageIdList, - path: std::path::PathBuf, -} - -impl Message { - pub fn from(mut entry: MailEntry) -> Result<Self> { - let parsed = entry.parsed()?; // escape now if invalid msg - let subject = parsed - .headers - .get_first_value("Subject") - .unwrap_or("[No Subject]".to_string()); - let id = parsed - .headers - .get_first_value("message-id") - .and_then(|m| { - msgidparse(&m).ok().and_then(|i| match i.len() { - 0 => None, - _ => Some(i[0].clone()), - }) - }) - .context("No valid message ID")?; - let mut references = msgidparse( - &parsed - .headers - .get_first_value("references") - .unwrap_or("".to_owned()), - )?; - if let Some(irt) = msgidparse( - &parsed - .headers - .get_first_value("in_reply_to") - .unwrap_or("".to_string()), - )? - .iter() - .next() - { - references.push(irt.to_string()) - } - Ok(Self { - subject, - id, - references, - path: entry.path().to_owned(), - }) - } -} diff --git a/src/utils.rs b/src/utils.rs @@ -1,7 +1,7 @@ use linkify::{LinkFinder, LinkKind}; // gpl licensed from wikipedia https://commons.wikimedia.org/wiki/File:Generic_Feed-icon.svg -pub const rss_svg: &str = r#" +pub const RSS_SVG: &str = r#" data:image/svg+xml,<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" id="RSSicon"