add id mapper to notmuch backend

This commit is contained in:
Clément DOUIN 2022-02-28 12:59:46 +01:00
parent ad1f97faed
commit 6606bd9f16
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
2 changed files with 92 additions and 58 deletions

View file

@ -3,7 +3,7 @@ use std::{convert::TryInto, fs};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use crate::{ use crate::{
backends::{Backend, NotmuchEnvelopes, NotmuchMbox, NotmuchMboxes}, backends::{Backend, IdMapper, NotmuchEnvelopes, NotmuchMbox, NotmuchMboxes},
config::{AccountConfig, NotmuchBackendConfig}, config::{AccountConfig, NotmuchBackendConfig},
mbox::Mboxes, mbox::Mboxes,
msg::{Envelopes, Msg}, msg::{Envelopes, Msg},
@ -11,6 +11,7 @@ use crate::{
pub struct NotmuchBackend<'a> { pub struct NotmuchBackend<'a> {
account_config: &'a AccountConfig, account_config: &'a AccountConfig,
notmuch_config: &'a NotmuchBackendConfig,
db: notmuch::Database, db: notmuch::Database,
} }
@ -21,6 +22,7 @@ impl<'a> NotmuchBackend<'a> {
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
account_config, account_config,
notmuch_config,
db: notmuch::Database::open( db: notmuch::Database::open(
notmuch_config.notmuch_database_dir.clone(), notmuch_config.notmuch_database_dir.clone(),
notmuch::DatabaseMode::ReadWrite, notmuch::DatabaseMode::ReadWrite,
@ -31,6 +33,64 @@ impl<'a> NotmuchBackend<'a> {
))?, ))?,
}) })
} }
fn _search_envelopes(
&mut self,
query: &str,
virt_mbox: &str,
page_size: usize,
page: usize,
) -> Result<Box<dyn Envelopes>> {
// Gets envelopes matching the given Notmuch query.
let query_builder = self
.db
.create_query(query)
.context(format!("cannot create notmuch query from {:?}", query))?;
let mut envelopes: NotmuchEnvelopes = query_builder
.search_messages()
.context(format!(
"cannot find notmuch envelopes from query {:?}",
query
))?
.try_into()
.context(format!(
"cannot parse notmuch envelopes from query {:?}",
query
))?;
// Calculates pagination boundaries.
let page_begin = page * page_size;
if page_begin > envelopes.len() {
return Err(anyhow!(format!(
"cannot find notmuch envelopes at page {:?} (out of bounds)",
page_begin + 1,
)));
}
let page_end = envelopes.len().min(page_begin + page_size);
// Sorts envelopes by most recent date.
envelopes.sort_by(|a, b| b.date.partial_cmp(&a.date).unwrap());
// Applies pagination boundaries.
envelopes.0 = envelopes[page_begin..page_end].to_owned();
// Appends id <=> hash entries to the id mapper cache file.
let short_hash_len = {
let mut mapper = IdMapper::new(&self.notmuch_config.notmuch_database_dir)?;
let entries = envelopes
.iter()
.map(|env| (env.hash.to_owned(), env.id.to_owned()))
.collect();
mapper.append(entries)?
};
// Shorten envelopes hash.
envelopes
.iter_mut()
.for_each(|env| env.hash = env.hash[0..short_hash_len].to_owned());
Ok(Box::new(envelopes))
}
} }
impl<'a> Backend<'a> for NotmuchBackend<'a> { impl<'a> Backend<'a> for NotmuchBackend<'a> {
@ -55,81 +115,44 @@ impl<'a> Backend<'a> for NotmuchBackend<'a> {
fn get_envelopes( fn get_envelopes(
&mut self, &mut self,
mbox: &str, virt_mbox: &str,
page_size: usize, page_size: usize,
page: usize, page: usize,
) -> Result<Box<dyn Envelopes>> { ) -> Result<Box<dyn Envelopes>> {
let query = self let query = self
.account_config .account_config
.mailboxes .mailboxes
.get(mbox) .get(virt_mbox)
.map(|s| s.as_str()) .map(|s| s.as_str())
.unwrap_or("all"); .unwrap_or("all");
let query_builder = self self._search_envelopes(query, virt_mbox, page_size, page)
.db
.create_query(query)
.context("cannot create notmuch query")?;
let mut envelopes: NotmuchEnvelopes = query_builder
.search_messages()
.context(format!(
"cannot find notmuch envelopes with query {:?}",
query
))?
.try_into()?;
envelopes.sort_by(|a, b| b.date.partial_cmp(&a.date).unwrap());
let page_begin = page * page_size;
if page_begin > envelopes.len() {
return Err(anyhow!(format!(
"cannot find notmuch envelopes at page {:?} (out of bounds)",
page_begin + 1,
)));
}
let page_end = envelopes.len().min(page_begin + page_size);
envelopes.0 = envelopes[page_begin..page_end].to_owned();
Ok(Box::new(envelopes))
} }
fn find_envelopes( fn search_envelopes(
&mut self, &mut self,
_mbox: &str, virt_mbox: &str,
query: &str, query: &str,
_sort: &str, _sort: &str,
page_size: usize, page_size: usize,
page: usize, page: usize,
) -> Result<Box<dyn Envelopes>> { ) -> Result<Box<dyn Envelopes>> {
let query_builder = self self._search_envelopes(query, virt_mbox, page_size, page)
.db
.create_query(query)
.context("cannot create notmuch query")?;
let mut envelopes: NotmuchEnvelopes = query_builder
.search_messages()
.context(format!(
"cannot find notmuch envelopes with query {:?}",
query
))?
.try_into()?;
// TODO: use sort from parameters instead
envelopes.sort_by(|a, b| b.date.partial_cmp(&a.date).unwrap());
let page_begin = page * page_size;
if page_begin > envelopes.len() {
return Err(anyhow!(format!(
"cannot find notmuch envelopes at page {:?} (out of bounds)",
page_begin + 1,
)));
}
let page_end = envelopes.len().min(page_begin + page_size);
envelopes.0 = envelopes[page_begin..page_end].to_owned();
Ok(Box::new(envelopes))
} }
fn add_msg(&mut self, _mbox: &str, _msg: &[u8], _flags: &str) -> Result<Box<dyn ToString>> { fn add_msg(&mut self, _mbox: &str, _msg: &[u8], _flags: &str) -> Result<Box<dyn ToString>> {
unimplemented!(); unimplemented!();
} }
fn get_msg(&mut self, _mbox: &str, id: &str) -> Result<Msg> { fn get_msg(&mut self, _mbox: &str, short_hash: &str) -> Result<Msg> {
let id = IdMapper::new(&self.notmuch_config.notmuch_database_dir)?
.find(short_hash)
.context(format!(
"cannot get notmuch message from short hash {:?}",
short_hash
))?;
let msg_filepath = self let msg_filepath = self
.db .db
.find_message(id) .find_message(&id)
.context(format!("cannot find notmuch message {:?}", id))? .context(format!("cannot find notmuch message {:?}", id))?
.ok_or_else(|| anyhow!("cannot find notmuch message {:?}", id))? .ok_or_else(|| anyhow!("cannot find notmuch message {:?}", id))?
.filename() .filename()
@ -148,10 +171,16 @@ impl<'a> Backend<'a> for NotmuchBackend<'a> {
unimplemented!(); unimplemented!();
} }
fn del_msg(&mut self, _mbox: &str, id: &str) -> Result<()> { fn del_msg(&mut self, _mbox: &str, short_hash: &str) -> Result<()> {
let id = IdMapper::new(&self.notmuch_config.notmuch_database_dir)?
.find(short_hash)
.context(format!(
"cannot get notmuch message from short hash {:?}",
short_hash
))?;
let msg_filepath = self let msg_filepath = self
.db .db
.find_message(id) .find_message(&id)
.context(format!("cannot find notmuch message {:?}", id))? .context(format!("cannot find notmuch message {:?}", id))?
.ok_or_else(|| anyhow!("cannot find notmuch message {:?}", id))? .ok_or_else(|| anyhow!("cannot find notmuch message {:?}", id))?
.filename() .filename()

View file

@ -51,6 +51,9 @@ pub struct NotmuchEnvelope {
/// Represents the id of the message. /// Represents the id of the message.
pub id: String, pub id: String,
/// Represents the MD5 hash of the message id.
pub hash: String,
/// Represents the tags of the message. /// Represents the tags of the message.
pub flags: Vec<String>, pub flags: Vec<String>,
@ -67,7 +70,7 @@ pub struct NotmuchEnvelope {
impl Table for NotmuchEnvelope { impl Table for NotmuchEnvelope {
fn head() -> Row { fn head() -> Row {
Row::new() Row::new()
.cell(Cell::new("ID").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())
@ -75,14 +78,14 @@ impl Table for NotmuchEnvelope {
} }
fn row(&self) -> Row { fn row(&self) -> Row {
let id = self.id.to_string(); let hash = self.hash.to_string();
let unseen = !self.flags.contains(&String::from("unread")); let unseen = !self.flags.contains(&String::from("unread"));
let flags = String::new(); let flags = String::new();
let subject = &self.subject; let subject = &self.subject;
let sender = &self.sender; let sender = &self.sender;
let date = &self.date; let date = &self.date;
Row::new() Row::new()
.cell(Cell::new(id).bold_if(unseen).red()) .cell(Cell::new(hash).bold_if(unseen).red())
.cell(Cell::new(flags).bold_if(unseen).white()) .cell(Cell::new(flags).bold_if(unseen).white())
.cell(Cell::new(subject).shrinkable().bold_if(unseen).green()) .cell(Cell::new(subject).shrinkable().bold_if(unseen).green())
.cell(Cell::new(sender).bold_if(unseen).blue()) .cell(Cell::new(sender).bold_if(unseen).blue())
@ -117,7 +120,8 @@ impl<'a> TryFrom<RawNotmuchEnvelope> for NotmuchEnvelope {
fn try_from(raw_envelope: RawNotmuchEnvelope) -> Result<Self, Self::Error> { fn try_from(raw_envelope: RawNotmuchEnvelope) -> Result<Self, Self::Error> {
info!("begin: try building envelope from notmuch parsed mail"); info!("begin: try building envelope from notmuch parsed mail");
let id = raw_envelope.id().trim().to_string(); let id = raw_envelope.id().to_string();
let hash = format!("{:x}", md5::compute(&id));
let subject = raw_envelope let subject = raw_envelope
.header("subject") .header("subject")
.context("cannot get header \"Subject\" from notmuch message")? .context("cannot get header \"Subject\" from notmuch message")?
@ -159,6 +163,7 @@ impl<'a> TryFrom<RawNotmuchEnvelope> for NotmuchEnvelope {
let envelope = Self { let envelope = Self {
id, id,
hash,
flags: raw_envelope.tags().collect(), flags: raw_envelope.tags().collect(),
subject, subject,
sender, sender,