make maildir envelopes selectable by short md5 hash

This commit is contained in:
Clément DOUIN 2022-02-27 10:23:58 +01:00
parent a2616fc1bd
commit c87512dbd4
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
4 changed files with 112 additions and 9 deletions

7
Cargo.lock generated
View file

@ -452,6 +452,7 @@ dependencies = [
"log", "log",
"maildir", "maildir",
"mailparse", "mailparse",
"md5",
"native-tls", "native-tls",
"notmuch", "notmuch",
"regex", "regex",
@ -712,6 +713,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "1.0.2" version = "1.0.2"

View file

@ -31,6 +31,7 @@ lettre = { version = "0.10.0-rc.1", features = ["serde"] }
log = "0.4.14" log = "0.4.14"
maildir = "0.6.0" maildir = "0.6.0"
mailparse = "0.13.6" mailparse = "0.13.6"
md5 = "0.7.0"
native-tls = "0.2.8" native-tls = "0.2.8"
notmuch = { version = "0.7.1", optional = true } notmuch = { version = "0.7.1", optional = true }
regex = "1.5.4" regex = "1.5.4"

View file

@ -1,5 +1,13 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use std::{convert::TryInto, fs, path::PathBuf}; use std::{
collections::HashSet,
convert::TryInto,
env::temp_dir,
fs::{self, OpenOptions},
io::{BufRead, BufReader, Write},
iter::FromIterator,
path::PathBuf,
};
use crate::{ use crate::{
backends::{Backend, MaildirEnvelopes, MaildirFlags, MaildirMboxes}, backends::{Backend, MaildirEnvelopes, MaildirFlags, MaildirMboxes},
@ -55,6 +63,48 @@ impl<'a> MaildirBackend<'a> {
.map(maildir::Maildir::from) .map(maildir::Maildir::from)
} }
} }
fn write_envelopes_cache(cache: &[u8]) -> Result<()> {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(temp_dir().join("himalaya-msg-id-hash-map"))
.context("cannot open maildir id hash map cache")?
.write(cache)
.map(|_| ())
.context("cannot write maildir id hash map cache")
}
fn get_id_from_short_hash(short_hash: &str) -> Result<String> {
let path = temp_dir().join("himalaya-msg-id-hash-map");
let file = OpenOptions::new()
.read(true)
.open(path)
.context("cannot open id hash map file")?;
let reader = BufReader::new(file);
let mut id_found = None;
for line in reader.lines() {
let line = line.context("cannot read id hash map line")?;
let line = line
.split_once(' ')
.ok_or_else(|| anyhow!("cannot parse id hash map line {:?}", line));
match line {
Ok((id, hash)) if hash.starts_with(short_hash) => {
if id_found.is_some() {
return Err(anyhow!(
"cannot find id from hash {:?}: multiple match found",
short_hash
));
} else {
id_found = Some(id.to_owned())
}
}
_ => continue,
}
}
id_found.ok_or_else(|| anyhow!("cannot find id from hash {:?}", short_hash))
}
} }
impl<'a> Backend<'a> for MaildirBackend<'a> { impl<'a> Backend<'a> for MaildirBackend<'a> {
@ -80,11 +130,48 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
page: usize, page: usize,
) -> Result<Box<dyn Envelopes>> { ) -> Result<Box<dyn Envelopes>> {
let mdir = self.get_mdir_from_name(mdir)?; let mdir = self.get_mdir_from_name(mdir)?;
let mut envelopes: MaildirEnvelopes = mdir let mut envelopes: MaildirEnvelopes = mdir
.list_cur() .list_cur()
.try_into() .try_into()
.context("cannot parse maildir envelopes from {:?}")?; .context("cannot parse maildir envelopes from {:?}")?;
Self::write_envelopes_cache(
envelopes
.iter()
.map(|env| format!("{} {:x}", env.id, md5::compute(&env.id)))
.collect::<Vec<_>>()
.join("\n")
.as_bytes(),
)?;
envelopes.sort_by(|a, b| b.date.partial_cmp(&a.date).unwrap()); envelopes.sort_by(|a, b| b.date.partial_cmp(&a.date).unwrap());
envelopes
.iter_mut()
.for_each(|env| env.id = format!("{:x}", md5::compute(&env.id)));
let mut short_id_len = 2;
loop {
let short_ids: Vec<_> = envelopes
.iter()
.map(|env| env.id[0..short_id_len].to_string())
.collect();
let short_ids_set: HashSet<String> = HashSet::from_iter(short_ids.iter().cloned());
if short_id_len > 32 {
break;
}
if short_ids.len() == short_ids_set.len() {
break;
}
short_id_len += 1;
}
envelopes
.iter_mut()
.for_each(|env| env.id = env.id[0..short_id_len].to_string());
let page_begin = page * page_size; let page_begin = page * page_size;
if page_begin > envelopes.len() { if page_begin > envelopes.len() {
@ -95,6 +182,7 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
} }
let page_end = envelopes.len().min(page_begin + page_size); let page_end = envelopes.len().min(page_begin + page_size);
envelopes.0 = envelopes[page_begin..page_end].to_owned(); envelopes.0 = envelopes[page_begin..page_end].to_owned();
Ok(Box::new(envelopes)) Ok(Box::new(envelopes))
} }
@ -123,19 +211,25 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
Ok(Box::new(id)) Ok(Box::new(id))
} }
fn get_msg(&mut self, mdir: &str, id: &str) -> Result<Msg> { fn get_msg(&mut self, mdir: &str, hash: &str) -> Result<Msg> {
let mdir = self.get_mdir_from_name(mdir)?; let mdir = self.get_mdir_from_name(mdir)?;
let mut mail_entry = mdir let id = Self::get_id_from_short_hash(hash)
.find(id) .context(format!("cannot get msg from hash {:?}", hash))?;
.ok_or_else(|| anyhow!("cannot find maildir message {:?} in {:?}", id, mdir.path()))?; let mut mail_entry = mdir.find(&id).ok_or_else(|| {
anyhow!(
"cannot find maildir message {:?} in {:?}",
hash,
mdir.path()
)
})?;
let parsed_mail = mail_entry.parsed().context(format!( let parsed_mail = mail_entry.parsed().context(format!(
"cannot parse maildir message {:?} in {:?}", "cannot parse maildir message {:?} in {:?}",
id, hash,
mdir.path() mdir.path()
))?; ))?;
Msg::from_parsed_mail(parsed_mail, self.account_config).context(format!( Msg::from_parsed_mail(parsed_mail, self.account_config).context(format!(
"cannot parse maildir message {:?} from {:?}", "cannot parse maildir message {:?} from {:?}",
id, hash,
mdir.path() mdir.path()
)) ))
} }

View file

@ -72,7 +72,7 @@ pub struct MaildirEnvelope {
impl Table for MaildirEnvelope { impl Table for MaildirEnvelope {
fn head() -> Row { fn head() -> Row {
Row::new() Row::new()
.cell(Cell::new("IDENTIFIER").bold().underline().white()) .cell(Cell::new("HASH").bold().underline().white())
.cell(Cell::new("FLAGS").bold().underline().white()) .cell(Cell::new("FLAGS").bold().underline().white())
.cell(Cell::new("SUBJECT").shrinkable().bold().underline().white()) .cell(Cell::new("SUBJECT").shrinkable().bold().underline().white())
.cell(Cell::new("SENDER").bold().underline().white()) .cell(Cell::new("SENDER").bold().underline().white())
@ -80,7 +80,7 @@ impl Table for MaildirEnvelope {
} }
fn row(&self) -> Row { fn row(&self) -> Row {
let id = self.id.to_string(); let id = self.id.clone();
let unseen = !self.flags.contains(&MaildirFlag::Seen); let unseen = !self.flags.contains(&MaildirFlag::Seen);
let flags = self.flags.to_symbols_string(); let flags = self.flags.to_symbols_string();
let subject = &self.subject; let subject = &self.subject;
@ -110,6 +110,7 @@ impl<'a> TryFrom<RawMaildirEnvelopes> for MaildirEnvelopes {
.context("cannot parse maildir mail entry")?; .context("cannot parse maildir mail entry")?;
envelopes.push(envelope); envelopes.push(envelope);
} }
Ok(MaildirEnvelopes(envelopes)) Ok(MaildirEnvelopes(envelopes))
} }
} }