mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-22 02:50:19 +00:00
make maildir envelopes selectable by short md5 hash
This commit is contained in:
parent
a2616fc1bd
commit
c87512dbd4
4 changed files with 112 additions and 9 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -452,6 +452,7 @@ dependencies = [
|
|||
"log",
|
||||
"maildir",
|
||||
"mailparse",
|
||||
"md5",
|
||||
"native-tls",
|
||||
"notmuch",
|
||||
"regex",
|
||||
|
@ -712,6 +713,12 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "1.0.2"
|
||||
|
|
|
@ -31,6 +31,7 @@ lettre = { version = "0.10.0-rc.1", features = ["serde"] }
|
|||
log = "0.4.14"
|
||||
maildir = "0.6.0"
|
||||
mailparse = "0.13.6"
|
||||
md5 = "0.7.0"
|
||||
native-tls = "0.2.8"
|
||||
notmuch = { version = "0.7.1", optional = true }
|
||||
regex = "1.5.4"
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
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::{
|
||||
backends::{Backend, MaildirEnvelopes, MaildirFlags, MaildirMboxes},
|
||||
|
@ -55,6 +63,48 @@ impl<'a> MaildirBackend<'a> {
|
|||
.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> {
|
||||
|
@ -80,11 +130,48 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
page: usize,
|
||||
) -> Result<Box<dyn Envelopes>> {
|
||||
let mdir = self.get_mdir_from_name(mdir)?;
|
||||
|
||||
let mut envelopes: MaildirEnvelopes = mdir
|
||||
.list_cur()
|
||||
.try_into()
|
||||
.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
|
||||
.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;
|
||||
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);
|
||||
envelopes.0 = envelopes[page_begin..page_end].to_owned();
|
||||
|
||||
Ok(Box::new(envelopes))
|
||||
}
|
||||
|
||||
|
@ -123,19 +211,25 @@ impl<'a> Backend<'a> for MaildirBackend<'a> {
|
|||
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 mut mail_entry = mdir
|
||||
.find(id)
|
||||
.ok_or_else(|| anyhow!("cannot find maildir message {:?} in {:?}", id, mdir.path()))?;
|
||||
let id = Self::get_id_from_short_hash(hash)
|
||||
.context(format!("cannot get msg from hash {:?}", hash))?;
|
||||
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!(
|
||||
"cannot parse maildir message {:?} in {:?}",
|
||||
id,
|
||||
hash,
|
||||
mdir.path()
|
||||
))?;
|
||||
Msg::from_parsed_mail(parsed_mail, self.account_config).context(format!(
|
||||
"cannot parse maildir message {:?} from {:?}",
|
||||
id,
|
||||
hash,
|
||||
mdir.path()
|
||||
))
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ pub struct MaildirEnvelope {
|
|||
impl Table for MaildirEnvelope {
|
||||
fn head() -> Row {
|
||||
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("SUBJECT").shrinkable().bold().underline().white())
|
||||
.cell(Cell::new("SENDER").bold().underline().white())
|
||||
|
@ -80,7 +80,7 @@ impl Table for MaildirEnvelope {
|
|||
}
|
||||
|
||||
fn row(&self) -> Row {
|
||||
let id = self.id.to_string();
|
||||
let id = self.id.clone();
|
||||
let unseen = !self.flags.contains(&MaildirFlag::Seen);
|
||||
let flags = self.flags.to_symbols_string();
|
||||
let subject = &self.subject;
|
||||
|
@ -110,6 +110,7 @@ impl<'a> TryFrom<RawMaildirEnvelopes> for MaildirEnvelopes {
|
|||
.context("cannot parse maildir mail entry")?;
|
||||
envelopes.push(envelope);
|
||||
}
|
||||
|
||||
Ok(MaildirEnvelopes(envelopes))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue