Aerogramme refactoring #57

Merged
quentin merged 16 commits from feat/more-imap-qol into main 2024-01-06 10:38:37 +00:00
7 changed files with 120 additions and 54 deletions
Showing only changes of commit 53dbf82cbc - Show all commits

View file

@ -8,7 +8,11 @@ use crate::mail::unique_ident::UniqueIdent;
pub struct Index<'a>(pub &'a UidIndex); pub struct Index<'a>(pub &'a UidIndex);
impl<'a> Index<'a> { impl<'a> Index<'a> {
pub fn fetch(self: &Index<'a>, sequence_set: &SequenceSet, by_uid: bool) -> Result<Vec<MailIndex<'a>>> { pub fn fetch(
self: &Index<'a>,
sequence_set: &SequenceSet,
by_uid: bool,
) -> Result<Vec<MailIndex<'a>>> {
let mail_vec = self let mail_vec = self
.0 .0
.idx_by_uid .idx_by_uid
@ -37,7 +41,13 @@ impl<'a> Index<'a> {
i: NonZeroU32::try_from(i as u32 + 1).unwrap(), i: NonZeroU32::try_from(i as u32 + 1).unwrap(),
uid: mail.0, uid: mail.0,
uuid: mail.1, uuid: mail.1,
flags: self.0.table.get(&mail.1).ok_or(anyhow!("mail is missing from index"))?.1.as_ref(), flags: self
.0
.table
.get(&mail.1)
.ok_or(anyhow!("mail is missing from index"))?
.1
.as_ref(),
}); });
} }
} else { } else {
@ -59,7 +69,13 @@ impl<'a> Index<'a> {
i, i,
uid: mail.0, uid: mail.0,
uuid: mail.1, uuid: mail.1,
flags: self.0.table.get(&mail.1).ok_or(anyhow!("mail is missing from index"))?.1.as_ref(), flags: self
.0
.table
.get(&mail.1)
.ok_or(anyhow!("mail is missing from index"))?
.1
.as_ref(),
}); });
} else { } else {
bail!("No such mail: {}", i); bail!("No such mail: {}", i);
@ -68,7 +84,6 @@ impl<'a> Index<'a> {
} }
Ok(mails) Ok(mails)
} }
} }
@ -76,5 +91,5 @@ pub struct MailIndex<'a> {
pub i: NonZeroU32, pub i: NonZeroU32,
pub uid: ImapUid, pub uid: ImapUid,
pub uuid: UniqueIdent, pub uuid: UniqueIdent,
pub flags: &'a Vec<String> pub flags: &'a Vec<String>,
} }

View file

@ -16,15 +16,14 @@ use eml_codec::{
part::{composite::Message, AnyPart}, part::{composite::Message, AnyPart},
}; };
use crate::mail::query::QueryResult; use crate::mail::query::QueryResult;
use crate::imap::attributes::AttributesProxy; use crate::imap::attributes::AttributesProxy;
use crate::imap::flags; use crate::imap::flags;
use crate::imap::imf_view::message_envelope; use crate::imap::imf_view::message_envelope;
use crate::imap::index::MailIndex;
use crate::imap::mime_view; use crate::imap::mime_view;
use crate::imap::response::Body; use crate::imap::response::Body;
use crate::imap::index::MailIndex;
pub struct MailView<'a> { pub struct MailView<'a> {
pub in_idx: MailIndex<'a>, pub in_idx: MailIndex<'a>,
@ -39,15 +38,17 @@ impl<'a> MailView<'a> {
query_result, query_result,
content: match query_result { content: match query_result {
QueryResult::FullResult { content, .. } => { QueryResult::FullResult { content, .. } => {
let (_, parsed) = eml_codec::parse_message(&content).or(Err(anyhow!("Invalid mail body")))?; let (_, parsed) =
eml_codec::parse_message(&content).or(Err(anyhow!("Invalid mail body")))?;
FetchedMail::new_from_message(parsed) FetchedMail::new_from_message(parsed)
}, }
QueryResult::PartialResult { metadata, .. } => { QueryResult::PartialResult { metadata, .. } => {
let (_, parsed) = eml_codec::parse_imf(&metadata.headers).or(Err(anyhow!("unable to parse email headers")))?; let (_, parsed) = eml_codec::parse_imf(&metadata.headers)
.or(Err(anyhow!("unable to parse email headers")))?;
FetchedMail::Partial(parsed) FetchedMail::Partial(parsed)
} }
QueryResult::IndexResult { .. } => FetchedMail::IndexOnly, QueryResult::IndexResult { .. } => FetchedMail::IndexOnly,
} },
}) })
} }
@ -66,12 +67,22 @@ impl<'a> MailView<'a> {
} }
fn rfc_822_size(&self) -> Result<MessageDataItem<'static>> { fn rfc_822_size(&self) -> Result<MessageDataItem<'static>> {
let sz = self.query_result.metadata().ok_or(anyhow!("mail metadata are required"))?.rfc822_size; let sz = self
.query_result
.metadata()
.ok_or(anyhow!("mail metadata are required"))?
.rfc822_size;
Ok(MessageDataItem::Rfc822Size(sz as u32)) Ok(MessageDataItem::Rfc822Size(sz as u32))
} }
fn rfc_822_header(&self) -> Result<MessageDataItem<'static>> { fn rfc_822_header(&self) -> Result<MessageDataItem<'static>> {
let hdrs: NString = self.query_result.metadata().ok_or(anyhow!("mail metadata are required"))?.headers.to_vec().try_into()?; let hdrs: NString = self
.query_result
.metadata()
.ok_or(anyhow!("mail metadata are required"))?
.headers
.to_vec()
.try_into()?;
Ok(MessageDataItem::Rfc822Header(hdrs)) Ok(MessageDataItem::Rfc822Header(hdrs))
} }
@ -142,7 +153,16 @@ impl<'a> MailView<'a> {
fn internal_date(&self) -> Result<MessageDataItem<'static>> { fn internal_date(&self) -> Result<MessageDataItem<'static>> {
let dt = Utc let dt = Utc
.fix() .fix()
.timestamp_opt(i64::try_from(self.query_result.metadata().ok_or(anyhow!("mail metadata were not fetched"))?.internaldate / 1000)?, 0) .timestamp_opt(
i64::try_from(
self.query_result
.metadata()
.ok_or(anyhow!("mail metadata were not fetched"))?
.internaldate
/ 1000,
)?,
0,
)
.earliest() .earliest()
.ok_or(anyhow!("Unable to parse internal date"))?; .ok_or(anyhow!("Unable to parse internal date"))?;
Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt))) Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt)))

View file

@ -13,17 +13,16 @@ use imap_codec::imap_types::search::SearchKey;
use imap_codec::imap_types::sequence::SequenceSet; use imap_codec::imap_types::sequence::SequenceSet;
use crate::mail::mailbox::Mailbox; use crate::mail::mailbox::Mailbox;
use crate::mail::snapshot::FrozenMailbox;
use crate::mail::query::QueryScope; use crate::mail::query::QueryScope;
use crate::mail::snapshot::FrozenMailbox;
use crate::mail::uidindex::{ImapUid, ImapUidvalidity}; use crate::mail::uidindex::{ImapUid, ImapUidvalidity};
use crate::imap::attributes::AttributesProxy; use crate::imap::attributes::AttributesProxy;
use crate::imap::flags; use crate::imap::flags;
use crate::imap::index::Index;
use crate::imap::mail_view::{MailView, SeenFlag}; use crate::imap::mail_view::{MailView, SeenFlag};
use crate::imap::response::Body; use crate::imap::response::Body;
use crate::imap::search; use crate::imap::search;
use crate::imap::index::Index;
const DEFAULT_FLAGS: [Flag; 5] = [ const DEFAULT_FLAGS: [Flag; 5] = [
Flag::Seen, Flag::Seen,
@ -40,7 +39,7 @@ const DEFAULT_FLAGS: [Flag; 5] = [
/// To do this, it keeps a variable `known_state` that corresponds to /// To do this, it keeps a variable `known_state` that corresponds to
/// what the client knows, and produces IMAP messages to be sent to the /// what the client knows, and produces IMAP messages to be sent to the
/// client that go along updates to `known_state`. /// client that go along updates to `known_state`.
pub struct MailboxView (pub FrozenMailbox); pub struct MailboxView(pub FrozenMailbox);
impl MailboxView { impl MailboxView {
/// Creates a new IMAP view into a mailbox. /// Creates a new IMAP view into a mailbox.
@ -258,11 +257,15 @@ impl MailboxView {
let mail_idx_list = self.index().fetch(sequence_set, *is_uid_fetch)?; let mail_idx_list = self.index().fetch(sequence_set, *is_uid_fetch)?;
// [2/6] Fetch the emails // [2/6] Fetch the emails
let uuids = mail_idx_list.iter().map(|midx| midx.uuid).collect::<Vec<_>>(); let uuids = mail_idx_list
.iter()
.map(|midx| midx.uuid)
.collect::<Vec<_>>();
let query_result = self.0.query(&uuids, query_scope).fetch().await?; let query_result = self.0.query(&uuids, query_scope).fetch().await?;
// [3/6] Derive an IMAP-specific view from the results, apply the filters // [3/6] Derive an IMAP-specific view from the results, apply the filters
let views = query_result.iter() let views = query_result
.iter()
.zip(mail_idx_list.into_iter()) .zip(mail_idx_list.into_iter())
.map(|(qr, midx)| MailView::new(qr, midx)) .map(|(qr, midx)| MailView::new(qr, midx))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
@ -284,7 +287,10 @@ impl MailboxView {
.filter(|(_mv, seen)| matches!(seen, SeenFlag::MustAdd)) .filter(|(_mv, seen)| matches!(seen, SeenFlag::MustAdd))
.map(|(mv, _seen)| async move { .map(|(mv, _seen)| async move {
let seen_flag = Flag::Seen.to_string(); let seen_flag = Flag::Seen.to_string();
self.0.mailbox.add_flags(*mv.query_result.uuid(), &[seen_flag]).await?; self.0
.mailbox
.add_flags(*mv.query_result.uuid(), &[seen_flag])
.await?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}) })
.collect::<FuturesOrdered<_>>() .collect::<FuturesOrdered<_>>()
@ -399,7 +405,8 @@ impl MailboxView {
// 1. Collecting all the possible flags in the mailbox // 1. Collecting all the possible flags in the mailbox
// 1.a Fetch them from our index // 1.a Fetch them from our index
let mut known_flags: Vec<Flag> = self.0 let mut known_flags: Vec<Flag> = self
.0
.snapshot .snapshot
.idx_by_flag .idx_by_flag
.flags() .flags()
@ -440,7 +447,8 @@ impl MailboxView {
pub(crate) fn unseen_count(&self) -> usize { pub(crate) fn unseen_count(&self) -> usize {
let total = self.0.snapshot.table.len(); let total = self.0.snapshot.table.len();
let seen = self.0 let seen = self
.0
.snapshot .snapshot
.idx_by_flag .idx_by_flag
.get(&Flag::Seen.to_string()) .get(&Flag::Seen.to_string())
@ -462,12 +470,12 @@ mod tests {
use std::fs; use std::fs;
use crate::cryptoblob; use crate::cryptoblob;
use crate::imap::index::MailIndex;
use crate::imap::mail_view::MailView; use crate::imap::mail_view::MailView;
use crate::imap::mime_view; use crate::imap::mime_view;
use crate::imap::index::MailIndex;
use crate::mail::mailbox::MailMeta; use crate::mail::mailbox::MailMeta;
use crate::mail::unique_ident;
use crate::mail::query::QueryResult; use crate::mail::query::QueryResult;
use crate::mail::unique_ident;
#[test] #[test]
fn mailview_body_ext() -> Result<()> { fn mailview_body_ext() -> Result<()> {

View file

@ -76,7 +76,8 @@ impl<'a> Criteria<'a> {
use SearchKey::*; use SearchKey::*;
match self.0 { match self.0 {
// IMF Headers // IMF Headers
Bcc(_) | Cc(_) | From(_) | Header(..) | SentBefore(_) | SentOn(_) | SentSince(_) | Subject(_) | To(_) => true, Bcc(_) | Cc(_) | From(_) | Header(..) | SentBefore(_) | SentOn(_) | SentSince(_)
| Subject(_) | To(_) => true,
// Internal Date is also stored in MailMeta // Internal Date is also stored in MailMeta
Before(_) | On(_) | Since(_) => true, Before(_) | On(_) | Since(_) => true,
// Message size is also stored in MailMeta // Message size is also stored in MailMeta

View file

@ -3,8 +3,8 @@ use std::io::Write;
pub mod incoming; pub mod incoming;
pub mod mailbox; pub mod mailbox;
pub mod snapshot;
pub mod query; pub mod query;
pub mod snapshot;
pub mod uidindex; pub mod uidindex;
pub mod unique_ident; pub mod unique_ident;
pub mod user; pub mod user;

View file

@ -1,13 +1,13 @@
use anyhow::{Result, anyhow};
use super::mailbox::MailMeta; use super::mailbox::MailMeta;
use super::snapshot::FrozenMailbox; use super::snapshot::FrozenMailbox;
use super::unique_ident::UniqueIdent;
use super::uidindex::IndexEntry; use super::uidindex::IndexEntry;
use super::unique_ident::UniqueIdent;
use anyhow::{anyhow, Result};
use futures::stream::{FuturesUnordered, StreamExt}; use futures::stream::{FuturesUnordered, StreamExt};
/// Query is in charge of fetching efficiently /// Query is in charge of fetching efficiently
/// requested data for a list of emails /// requested data for a list of emails
pub struct Query<'a,'b> { pub struct Query<'a, 'b> {
pub frozen: &'a FrozenMailbox, pub frozen: &'a FrozenMailbox,
pub emails: &'b [UniqueIdent], pub emails: &'b [UniqueIdent],
pub scope: QueryScope, pub scope: QueryScope,
@ -20,7 +20,7 @@ pub enum QueryScope {
Full, Full,
} }
impl<'a,'b> Query<'a,'b> { impl<'a, 'b> Query<'a, 'b> {
pub async fn fetch(&self) -> Result<Vec<QueryResult<'a>>> { pub async fn fetch(&self) -> Result<Vec<QueryResult<'a>>> {
match self.scope { match self.scope {
QueryScope::Index => self.index(), QueryScope::Index => self.index(),
@ -32,12 +32,10 @@ impl<'a,'b> Query<'a,'b> {
// --- functions below are private *for reasons* // --- functions below are private *for reasons*
fn index(&self) -> Result<Vec<QueryResult<'a>>> { fn index(&self) -> Result<Vec<QueryResult<'a>>> {
self self.emails
.emails
.iter() .iter()
.map(|uuid| { .map(|uuid| {
self self.frozen
.frozen
.snapshot .snapshot
.table .table
.get(uuid) .get(uuid)
@ -52,7 +50,11 @@ impl<'a,'b> Query<'a,'b> {
let result = meta let result = meta
.into_iter() .into_iter()
.zip(self.index()?) .zip(self.index()?)
.map(|(metadata, index)| index.into_partial(metadata).expect("index to be IndexResult")) .map(|(metadata, index)| {
index
.into_partial(metadata)
.expect("index to be IndexResult")
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(result) Ok(result)
} }
@ -66,10 +68,17 @@ impl<'a,'b> Query<'a,'b> {
meta_list meta_list
.into_iter() .into_iter()
.map(|meta| async move { .map(|meta| async move {
let content = self.frozen.mailbox.fetch_full( let content = self
.frozen
.mailbox
.fetch_full(
*meta.uuid(), *meta.uuid(),
&meta.metadata().expect("meta to be PartialResult").message_key &meta
).await?; .metadata()
.expect("meta to be PartialResult")
.message_key,
)
.await?;
Ok(meta.into_full(content).expect("meta to be PartialResult")) Ok(meta.into_full(content).expect("meta to be PartialResult"))
}) })
@ -96,7 +105,7 @@ pub enum QueryResult<'a> {
index: &'a IndexEntry, index: &'a IndexEntry,
metadata: MailMeta, metadata: MailMeta,
content: Vec<u8>, content: Vec<u8>,
} },
} }
impl<'a> QueryResult<'a> { impl<'a> QueryResult<'a> {
pub fn uuid(&self) -> &UniqueIdent { pub fn uuid(&self) -> &UniqueIdent {
@ -134,14 +143,27 @@ impl<'a> QueryResult<'a> {
fn into_partial(self, metadata: MailMeta) -> Option<Self> { fn into_partial(self, metadata: MailMeta) -> Option<Self> {
match self { match self {
Self::IndexResult { uuid, index } => Some(Self::PartialResult { uuid, index, metadata }), Self::IndexResult { uuid, index } => Some(Self::PartialResult {
uuid,
index,
metadata,
}),
_ => None, _ => None,
} }
} }
fn into_full(self, content: Vec<u8>) -> Option<Self> { fn into_full(self, content: Vec<u8>) -> Option<Self> {
match self { match self {
Self::PartialResult { uuid, index, metadata } => Some(Self::FullResult { uuid, index, metadata, content }), Self::PartialResult {
uuid,
index,
metadata,
} => Some(Self::FullResult {
uuid,
index,
metadata,
content,
}),
_ => None, _ => None,
} }
} }

View file

@ -3,9 +3,9 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use super::mailbox::Mailbox; use super::mailbox::Mailbox;
use super::query::{Query, QueryScope};
use super::uidindex::UidIndex; use super::uidindex::UidIndex;
use super::unique_ident::UniqueIdent; use super::unique_ident::UniqueIdent;
use super::query::{Query, QueryScope};
/// A Frozen Mailbox has a snapshot of the current mailbox /// A Frozen Mailbox has a snapshot of the current mailbox
/// state that is desynchronized with the real mailbox state. /// state that is desynchronized with the real mailbox state.