From a84ba4d42fcdb38be514178eb9fced777ba76055 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Sat, 6 Jan 2024 11:07:53 +0100 Subject: [PATCH] Mailbox View made more readable --- src/imap/index.rs | 80 ++++++++++++++++++++++++++ src/imap/mail_view.rs | 94 +++++++++++++------------------ src/imap/mailbox_view.rs | 119 +++++++-------------------------------- src/imap/mod.rs | 2 +- src/imap/selectors.rs | 100 -------------------------------- src/mail/query.rs | 7 ++- 6 files changed, 143 insertions(+), 259 deletions(-) create mode 100644 src/imap/index.rs delete mode 100644 src/imap/selectors.rs diff --git a/src/imap/index.rs b/src/imap/index.rs new file mode 100644 index 0000000..347222c --- /dev/null +++ b/src/imap/index.rs @@ -0,0 +1,80 @@ +use std::num::NonZeroU32; + +use anyhow::{anyhow, bail, Result}; +use imap_codec::imap_types::sequence::{self, SequenceSet}; + +use crate::mail::uidindex::{ImapUid, UidIndex}; +use crate::mail::unique_ident::UniqueIdent; + +pub struct Index<'a>(pub &'a UidIndex); +impl<'a> Index<'a> { + pub fn fetch(self: &Index<'a>, sequence_set: &SequenceSet, by_uid: bool) -> Result>> { + let mail_vec = self + .0 + .idx_by_uid + .iter() + .map(|(uid, uuid)| (*uid, *uuid)) + .collect::>(); + + let mut mails = vec![]; + + if by_uid { + if mail_vec.is_empty() { + return Ok(vec![]); + } + let iter_strat = sequence::Strategy::Naive { + largest: mail_vec.last().unwrap().0, + }; + + let mut i = 0; + for uid in sequence_set.iter(iter_strat) { + while mail_vec.get(i).map(|mail| mail.0 < uid).unwrap_or(false) { + i += 1; + } + if let Some(mail) = mail_vec.get(i) { + if mail.0 == uid { + mails.push(MailIndex { + i: NonZeroU32::try_from(i as u32 + 1).unwrap(), + uid: mail.0, + uuid: mail.1, + flags: self.0.table.get(&mail.1).ok_or(anyhow!("mail is missing from index"))?.1.as_ref(), + }); + } + } else { + break; + } + } + } else { + if mail_vec.is_empty() { + bail!("No such message (mailbox is empty)"); + } + + let iter_strat = sequence::Strategy::Naive { + largest: NonZeroU32::try_from((mail_vec.len()) as u32).unwrap(), + }; + + for i in sequence_set.iter(iter_strat) { + if let Some(mail) = mail_vec.get(i.get() as usize - 1) { + mails.push(MailIndex { + i, + uid: mail.0, + uuid: mail.1, + flags: self.0.table.get(&mail.1).ok_or(anyhow!("mail is missing from index"))?.1.as_ref(), + }); + } else { + bail!("No such mail: {}", i); + } + } + } + + Ok(mails) + + } +} + +pub struct MailIndex<'a> { + pub i: NonZeroU32, + pub uid: ImapUid, + pub uuid: UniqueIdent, + pub flags: &'a Vec +} diff --git a/src/imap/mail_view.rs b/src/imap/mail_view.rs index 94215dc..1f87f02 100644 --- a/src/imap/mail_view.rs +++ b/src/imap/mail_view.rs @@ -1,9 +1,9 @@ use std::num::NonZeroU32; -use anyhow::{anyhow, bail, Result, Context}; +use anyhow::{anyhow, bail, Result}; use chrono::{Offset, TimeZone, Utc}; -use imap_codec::imap_types::core::{IString, NString}; +use imap_codec::imap_types::core::NString; use imap_codec::imap_types::datetime::DateTime; use imap_codec::imap_types::fetch::{ MessageDataItem, MessageDataItemName, Section as FetchSection, @@ -16,87 +16,73 @@ use eml_codec::{ part::{composite::Message, AnyPart}, }; + +use crate::mail::query::QueryResult; + use crate::imap::attributes::AttributesProxy; use crate::imap::flags; use crate::imap::imf_view::message_envelope; -use crate::imap::mailbox_view::MailIdentifiers; use crate::imap::mime_view; use crate::imap::response::Body; -use crate::mail::query::QueryResult; +use crate::imap::index::MailIndex; pub struct MailView<'a> { + pub in_idx: MailIndex<'a>, pub query_result: &'a QueryResult<'a>, pub content: FetchedMail<'a>, } impl<'a> MailView<'a> { - pub fn new(query_result: &'a QueryResult<'a>) -> Result { + pub fn new(query_result: &'a QueryResult<'a>, in_idx: MailIndex<'a>) -> Result> { Ok(Self { + in_idx, query_result, content: match query_result { QueryResult::FullResult { content, .. } => { - let (_, parsed) = eml_codec::parse_message(content).context("Invalid mail body")?; + let (_, parsed) = eml_codec::parse_message(&content).or(Err(anyhow!("Invalid mail body")))?; FetchedMail::new_from_message(parsed) }, QueryResult::PartialResult { metadata, .. } => { - let (_, parsed) = eml_codec::parse_imf(&metadata.headers).context("Invalid mail headers")?; + let (_, parsed) = eml_codec::parse_imf(&metadata.headers).or(Err(anyhow!("unable to parse email headers")))?; FetchedMail::Partial(parsed) } - QueryResult::IndexResult { .. } => FetchedMail::None, + QueryResult::IndexResult { .. } => FetchedMail::IndexOnly, } }) } - + fn uid(&self) -> MessageDataItem<'static> { - MessageDataItem::Uid(self.ids.uid.clone()) + MessageDataItem::Uid(self.in_idx.uid.clone()) } fn flags(&self) -> MessageDataItem<'static> { MessageDataItem::Flags( - self.flags + self.in_idx + .flags .iter() .filter_map(|f| flags::from_str(f)) .collect(), ) } - fn rfc_822_size(&self) -> MessageDataItem<'static> { - MessageDataItem::Rfc822Size(self.meta.rfc822_size as u32) + fn rfc_822_size(&self) -> Result> { + let sz = self.query_result.metadata().ok_or(anyhow!("mail metadata are required"))?.rfc822_size; + Ok(MessageDataItem::Rfc822Size(sz as u32)) } - fn rfc_822_header(&self) -> MessageDataItem<'static> { - MessageDataItem::Rfc822Header(NString( - self.meta - .headers - .to_vec() - .try_into() - .ok() - .map(IString::Literal), - )) + fn rfc_822_header(&self) -> Result> { + let hdrs: NString = self.query_result.metadata().ok_or(anyhow!("mail metadata are required"))?.headers.to_vec().try_into()?; + Ok(MessageDataItem::Rfc822Header(hdrs)) } fn rfc_822_text(&self) -> Result> { - Ok(MessageDataItem::Rfc822Text(NString( - self.content - .as_full()? - .raw_body - .to_vec() - .try_into() - .ok() - .map(IString::Literal), - ))) + let txt: NString = self.content.as_full()?.raw_body.to_vec().try_into()?; + Ok(MessageDataItem::Rfc822Text(txt)) } fn rfc822(&self) -> Result> { - Ok(MessageDataItem::Rfc822(NString( - self.content - .as_full()? - .raw_part - .to_vec() - .try_into() - .ok() - .map(IString::Literal), - ))) + let full: NString = self.content.as_full()?.raw_part.to_vec().try_into()?; + Ok(MessageDataItem::Rfc822(full)) } fn envelope(&self) -> MessageDataItem<'static> { @@ -119,16 +105,16 @@ impl<'a> MailView<'a> { /// peek does not implicitly set the \Seen flag /// eg. BODY[HEADER.FIELDS (DATE FROM)] /// eg. BODY[]<0.2048> - fn body_ext<'b>( + fn body_ext( &self, - section: &Option>, + section: &Option>, partial: &Option<(u32, NonZeroU32)>, peek: &bool, - ) -> Result<(MessageDataItem<'b>, SeenFlag)> { + ) -> Result<(MessageDataItem<'static>, SeenFlag)> { // Manage Seen flag let mut seen = SeenFlag::DoNothing; let seen_flag = Flag::Seen.to_string(); - if !peek && !self.flags.iter().any(|x| *x == seen_flag) { + if !peek && !self.in_idx.flags.iter().any(|x| *x == seen_flag) { // Add \Seen flag //self.mailbox.add_flags(uuid, &[seen_flag]).await?; seen = SeenFlag::MustAdd; @@ -141,7 +127,7 @@ impl<'a> MailView<'a> { mime_view::BodySection::Slice { body, origin_octet } => (body, Some(origin_octet)), }; - let data = NString(text.to_vec().try_into().ok().map(IString::Literal)); + let data: NString = text.to_vec().try_into()?; return Ok(( MessageDataItem::BodyExt { @@ -156,13 +142,13 @@ impl<'a> MailView<'a> { fn internal_date(&self) -> Result> { let dt = Utc .fix() - .timestamp_opt(i64::try_from(self.meta.internaldate / 1000)?, 0) + .timestamp_opt(i64::try_from(self.query_result.metadata().ok_or(anyhow!("mail metadata were not fetched"))?.internaldate / 1000)?, 0) .earliest() .ok_or(anyhow!("Unable to parse internal date"))?; Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt))) } - pub fn filter<'b>(&self, ap: &AttributesProxy) -> Result<(Body<'static>, SeenFlag)> { + pub fn filter(&self, ap: &AttributesProxy) -> Result<(Body<'static>, SeenFlag)> { let mut seen = SeenFlag::DoNothing; let res_attrs = ap .attrs @@ -170,8 +156,8 @@ impl<'a> MailView<'a> { .map(|attr| match attr { MessageDataItemName::Uid => Ok(self.uid()), MessageDataItemName::Flags => Ok(self.flags()), - MessageDataItemName::Rfc822Size => Ok(self.rfc_822_size()), - MessageDataItemName::Rfc822Header => Ok(self.rfc_822_header()), + MessageDataItemName::Rfc822Size => self.rfc_822_size(), + MessageDataItemName::Rfc822Header => self.rfc_822_header(), MessageDataItemName::Rfc822Text => self.rfc_822_text(), MessageDataItemName::Rfc822 => self.rfc822(), MessageDataItemName::Envelope => Ok(self.envelope()), @@ -192,7 +178,7 @@ impl<'a> MailView<'a> { Ok(( Body::Data(Data::Fetch { - seq: self.ids.i, + seq: self.in_idx.i, items: res_attrs.try_into()?, }), seen, @@ -208,19 +194,15 @@ pub enum SeenFlag { // ------------------- pub enum FetchedMail<'a> { - None, + IndexOnly, Partial(imf::Imf<'a>), Full(AnyPart<'a>), } impl<'a> FetchedMail<'a> { pub fn new_from_message(msg: Message<'a>) -> Self { - FetchedMail::Full(AnyPart::Msg(msg)) + Self::Full(AnyPart::Msg(msg)) } - /*fn new_from_header(hdr: imf::Imf<'a>) -> Self { - FetchedMail::Partial(hdr) - }*/ - fn as_anypart(&self) -> Result<&AnyPart<'a>> { match self { FetchedMail::Full(x) => Ok(&x), diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index 9cc72c1..5bc6f87 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -1,7 +1,7 @@ use std::num::NonZeroU32; use std::sync::Arc; -use anyhow::{anyhow, bail, Error, Result}; +use anyhow::{anyhow, Error, Result}; use futures::stream::{FuturesOrdered, StreamExt}; @@ -10,19 +10,19 @@ use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItem use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType}; use imap_codec::imap_types::response::{Code, Data, Status}; use imap_codec::imap_types::search::SearchKey; -use imap_codec::imap_types::sequence::{self, SequenceSet}; +use imap_codec::imap_types::sequence::SequenceSet; use crate::mail::mailbox::Mailbox; use crate::mail::snapshot::FrozenMailbox; use crate::mail::query::QueryScope; use crate::mail::uidindex::{ImapUid, ImapUidvalidity}; -use crate::mail::unique_ident::UniqueIdent; use crate::imap::attributes::AttributesProxy; use crate::imap::flags; use crate::imap::mail_view::{MailView, SeenFlag}; use crate::imap::response::Body; -use crate::imap::search; +//use crate::imap::search; +use crate::imap::index::Index; const DEFAULT_FLAGS: [Flag; 5] = [ @@ -147,7 +147,7 @@ impl MailboxView { let flags = flags.iter().map(|x| x.to_string()).collect::>(); - let mails = self.get_mail_ids(sequence_set, *is_uid_store)?; + let mails = self.index().fetch(sequence_set, *is_uid_store)?; for mi in mails.iter() { match kind { StoreType::Add => { @@ -190,7 +190,7 @@ impl MailboxView { to: Arc, is_uid_copy: &bool, ) -> Result<(ImapUidvalidity, Vec<(ImapUid, ImapUid)>)> { - let mails = self.get_mail_ids(sequence_set, *is_uid_copy)?; + let mails = self.index().fetch(sequence_set, *is_uid_copy)?; let mut new_uuids = vec![]; for mi in mails.iter() { @@ -217,7 +217,7 @@ impl MailboxView { to: Arc, is_uid_copy: &bool, ) -> Result<(ImapUidvalidity, Vec<(ImapUid, ImapUid)>, Vec>)> { - let mails = self.get_mail_ids(sequence_set, *is_uid_copy)?; + let mails = self.index().fetch(sequence_set, *is_uid_copy)?; for mi in mails.iter() { to.move_from(&self.0.mailbox, mi.uuid).await?; @@ -255,16 +255,17 @@ impl MailboxView { true => QueryScope::Full, _ => QueryScope::Partial, }; - let mids = MailIdentifiersList(self.get_mail_ids(sequence_set, *is_uid_fetch)?); - let uuids = mids.uuids(); + let mail_idx_list = self.index().fetch(sequence_set, *is_uid_fetch)?; // [2/6] Fetch the emails + let uuids = mail_idx_list.iter().map(|midx| midx.uuid).collect::>(); let query = self.0.query(&uuids, query_scope); let query_result = query.fetch().await?; // [3/6] Derive an IMAP-specific view from the results, apply the filters let views = query_result.iter() - .map(MailView::new) + .zip(mail_idx_list.into_iter()) + .map(|(qr, midx)| MailView::new(qr, midx)) .collect::, _>>()?; // [4/6] Apply the IMAP transformation to keep only relevant fields @@ -296,9 +297,10 @@ impl MailboxView { pub async fn search<'a>( &self, _charset: &Option>, - search_key: &SearchKey<'a>, - uid: bool, + _search_key: &SearchKey<'a>, + _uid: bool, ) -> Result>> { + /* // 1. Compute the subset of sequence identifiers we need to fetch let query = search::Criteria(search_key); let (seq_set, seq_type) = query.to_sequence_set(); @@ -313,79 +315,15 @@ impl MailboxView { let _need_body = query.need_body(); Ok(vec![Body::Data(Data::Search(mail_u32))]) + */ + unimplemented!() } // ---- - - // Gets the IMAP ID, the IMAP UIDs and, the Aerogramme UUIDs of mails identified by a SequenceSet of - // sequence numbers (~ IMAP selector) - fn get_mail_ids( - &self, - sequence_set: &SequenceSet, - by_uid: bool, - ) -> Result> { - let mail_vec = self - .0 - .snapshot - .idx_by_uid - .iter() - .map(|(uid, uuid)| (*uid, *uuid)) - .collect::>(); - - let mut mails = vec![]; - - if by_uid { - if mail_vec.is_empty() { - return Ok(vec![]); - } - let iter_strat = sequence::Strategy::Naive { - largest: mail_vec.last().unwrap().0, - }; - - let mut i = 0; - for uid in sequence_set.iter(iter_strat) { - while mail_vec.get(i).map(|mail| mail.0 < uid).unwrap_or(false) { - i += 1; - } - if let Some(mail) = mail_vec.get(i) { - if mail.0 == uid { - mails.push(MailIdentifiers { - i: NonZeroU32::try_from(i as u32 + 1).unwrap(), - uid: mail.0, - uuid: mail.1, - }); - } - } else { - break; - } - } - } else { - if mail_vec.is_empty() { - bail!("No such message (mailbox is empty)"); - } - - let iter_strat = sequence::Strategy::Naive { - largest: NonZeroU32::try_from((mail_vec.len()) as u32).unwrap(), - }; - - for i in sequence_set.iter(iter_strat) { - if let Some(mail) = mail_vec.get(i.get() as usize - 1) { - mails.push(MailIdentifiers { - i, - uid: mail.0, - uuid: mail.1, - }); - } else { - bail!("No such mail: {}", i); - } - } - } - - Ok(mails) + fn index<'a>(&'a self) -> Index<'a> { + Index(&self.0.snapshot) } - // ---- - /// Produce an OK [UIDVALIDITY _] message corresponding to `known_state` fn uidvalidity_status(&self) -> Result> { let uid_validity = Status::ok( @@ -501,25 +439,6 @@ impl MailboxView { } } -pub struct MailIdentifiers { - pub i: NonZeroU32, - pub uid: ImapUid, - pub uuid: UniqueIdent, -} -pub struct MailIdentifiersList(Vec); - -impl MailIdentifiersList { - fn ids(&self) -> Vec { - self.0.iter().map(|mi| mi.i).collect() - } - fn uids(&self) -> Vec { - self.0.iter().map(|mi| mi.uid).collect() - } - fn uuids(&self) -> Vec { - self.0.iter().map(|mi| mi.uuid).collect() - } -} - #[cfg(test)] mod tests { use super::*; @@ -558,7 +477,7 @@ mod tests { message_key: key, rfc822_size: 8usize, }; - let ids = MailIdentifiers { + let ids = MailIndex { i: NonZeroU32::MIN, uid: NonZeroU32::MIN, uuid: unique_ident::gen_ident(), diff --git a/src/imap/mod.rs b/src/imap/mod.rs index ea34629..4142ef9 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -4,12 +4,12 @@ mod command; mod flags; mod flow; mod imf_view; +mod index; mod mail_view; mod mailbox_view; mod mime_view; mod response; mod search; -mod selectors; mod session; use std::net::SocketAddr; diff --git a/src/imap/selectors.rs b/src/imap/selectors.rs deleted file mode 100644 index 09320c3..0000000 --- a/src/imap/selectors.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::iter::zip; - -use anyhow::{anyhow, Result}; - -use crate::cryptoblob::Key; -use crate::imap::mail_view::{FetchedMail, MailView}; -use crate::imap::mailbox_view::MailIdentifiers; -use crate::mail::mailbox::MailMeta; -use crate::mail::unique_ident::UniqueIdent; - -pub struct BodyIdentifier<'a> { - pub msg_uuid: &'a UniqueIdent, - pub msg_key: &'a Key, -} - -#[derive(Default)] -pub struct MailSelectionBuilder<'a> { - //attrs: AttributeProxy, - mail_count: usize, - need_body: bool, - mi: &'a [MailIdentifiers], - meta: &'a [MailMeta], - flags: &'a [&'a Vec], - bodies: &'a [Vec], -} - -impl<'a> MailSelectionBuilder<'a> { - pub fn new(need_body: bool, mail_count: usize) -> Self { - Self { - mail_count, - need_body, - ..MailSelectionBuilder::default() - } - } - - pub fn with_mail_identifiers(&mut self, mi: &'a [MailIdentifiers]) -> &mut Self { - self.mi = mi; - self - } - - pub fn with_metadata(&mut self, meta: &'a [MailMeta]) -> &mut Self { - self.meta = meta; - self - } - - pub fn with_flags(&mut self, flags: &'a [&'a Vec]) -> &mut Self { - self.flags = flags; - self - } - - pub fn bodies_to_collect(&self) -> Vec { - if !self.need_body { - return vec![]; - } - zip(self.mi, self.meta) - .map(|(mi, meta)| BodyIdentifier { - msg_uuid: &mi.uuid, - msg_key: &meta.message_key, - }) - .collect::>() - } - - pub fn with_bodies(&mut self, rbodies: &'a [Vec]) -> &mut Self { - self.bodies = rbodies; - self - } - - pub fn build(&self) -> Result>> { - let mut bodies = vec![]; - - if !self.need_body { - for m in self.meta.iter() { - let (_, hdrs) = - eml_codec::parse_imf(&m.headers).or(Err(anyhow!("Invalid mail headers")))?; - bodies.push(FetchedMail::Partial(hdrs)); - } - } else { - for rb in self.bodies.iter() { - let (_, p) = eml_codec::parse_message(&rb).or(Err(anyhow!("Invalid mail body")))?; - bodies.push(FetchedMail::new_from_message(p)); - } - } - - if self.mi.len() != self.mail_count && self.meta.len() != self.mail_count - || self.flags.len() != self.mail_count - || bodies.len() != self.mail_count - { - return Err(anyhow!("Can't build a mail view selection as parts were not correctly registered into the builder.")); - } - - Ok(zip(self.mi, zip(self.meta, zip(self.flags, bodies))) - .map(|(ids, (meta, (flags, content)))| MailView { - ids, - meta, - flags, - content, - }) - .collect()) - } -} diff --git a/src/mail/query.rs b/src/mail/query.rs index 5beff37..70feb89 100644 --- a/src/mail/query.rs +++ b/src/mail/query.rs @@ -13,6 +13,7 @@ pub struct Query<'a,'b> { pub scope: QueryScope, } +#[allow(dead_code)] pub enum QueryScope { Index, Partial, @@ -106,6 +107,7 @@ impl<'a> QueryResult<'a> { } } + #[allow(dead_code)] pub fn index(&self) -> &IndexEntry { match self { Self::IndexResult { index, .. } => index, @@ -114,7 +116,7 @@ impl<'a> QueryResult<'a> { } } - pub fn metadata(&self) -> Option<&MailMeta> { + pub fn metadata(&'a self) -> Option<&'a MailMeta> { match self { Self::IndexResult { .. } => None, Self::PartialResult { metadata, .. } => Some(metadata), @@ -122,7 +124,8 @@ impl<'a> QueryResult<'a> { } } - pub fn content(&self) -> Option<&[u8]> { + #[allow(dead_code)] + pub fn content(&'a self) -> Option<&'a [u8]> { match self { Self::FullResult { content, .. } => Some(content), _ => None,