mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-24 20:10:23 +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",
|
"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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue