diff --git a/src/imap/index.rs b/src/imap/index.rs index 347222c..01dd2ef 100644 --- a/src/imap/index.rs +++ b/src/imap/index.rs @@ -8,7 +8,11 @@ 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>> { + pub fn fetch( + self: &Index<'a>, + sequence_set: &SequenceSet, + by_uid: bool, + ) -> Result>> { let mail_vec = self .0 .idx_by_uid @@ -37,7 +41,13 @@ impl<'a> Index<'a> { 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(), + flags: self + .0 + .table + .get(&mail.1) + .ok_or(anyhow!("mail is missing from index"))? + .1 + .as_ref(), }); } } else { @@ -59,7 +69,13 @@ impl<'a> Index<'a> { 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(), + flags: self + .0 + .table + .get(&mail.1) + .ok_or(anyhow!("mail is missing from index"))? + .1 + .as_ref(), }); } else { bail!("No such mail: {}", i); @@ -68,7 +84,6 @@ impl<'a> Index<'a> { } Ok(mails) - } } @@ -76,5 +91,5 @@ pub struct MailIndex<'a> { pub i: NonZeroU32, pub uid: ImapUid, pub uuid: UniqueIdent, - pub flags: &'a Vec + pub flags: &'a Vec, } diff --git a/src/imap/mail_view.rs b/src/imap/mail_view.rs index 1f87f02..de9bfe3 100644 --- a/src/imap/mail_view.rs +++ b/src/imap/mail_view.rs @@ -16,15 +16,14 @@ 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::index::MailIndex; use crate::imap::mime_view; use crate::imap::response::Body; -use crate::imap::index::MailIndex; pub struct MailView<'a> { pub in_idx: MailIndex<'a>, @@ -39,18 +38,20 @@ impl<'a> MailView<'a> { query_result, content: match query_result { 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) - }, + } 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) } QueryResult::IndexResult { .. } => FetchedMail::IndexOnly, - } + }, }) } - + fn uid(&self) -> MessageDataItem<'static> { MessageDataItem::Uid(self.in_idx.uid.clone()) } @@ -66,12 +67,22 @@ impl<'a> MailView<'a> { } fn rfc_822_size(&self) -> Result> { - 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)) } 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()?; + let hdrs: NString = self + .query_result + .metadata() + .ok_or(anyhow!("mail metadata are required"))? + .headers + .to_vec() + .try_into()?; Ok(MessageDataItem::Rfc822Header(hdrs)) } @@ -142,7 +153,16 @@ impl<'a> MailView<'a> { fn internal_date(&self) -> Result> { let dt = Utc .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() .ok_or(anyhow!("Unable to parse internal date"))?; Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt))) diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index 6a4724d..e4ffdcd 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -13,17 +13,16 @@ use imap_codec::imap_types::search::SearchKey; 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::snapshot::FrozenMailbox; use crate::mail::uidindex::{ImapUid, ImapUidvalidity}; use crate::imap::attributes::AttributesProxy; use crate::imap::flags; +use crate::imap::index::Index; use crate::imap::mail_view::{MailView, SeenFlag}; use crate::imap::response::Body; use crate::imap::search; -use crate::imap::index::Index; - const DEFAULT_FLAGS: [Flag; 5] = [ Flag::Seen, @@ -40,7 +39,7 @@ const DEFAULT_FLAGS: [Flag; 5] = [ /// 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 /// client that go along updates to `known_state`. -pub struct MailboxView (pub FrozenMailbox); +pub struct MailboxView(pub FrozenMailbox); impl MailboxView { /// 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)?; // [2/6] Fetch the emails - let uuids = mail_idx_list.iter().map(|midx| midx.uuid).collect::>(); + let uuids = mail_idx_list + .iter() + .map(|midx| midx.uuid) + .collect::>(); let query_result = self.0.query(&uuids, query_scope).fetch().await?; - + // [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()) .map(|(qr, midx)| MailView::new(qr, midx)) .collect::, _>>()?; @@ -284,7 +287,10 @@ impl MailboxView { .filter(|(_mv, seen)| matches!(seen, SeenFlag::MustAdd)) .map(|(mv, _seen)| async move { 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>(()) }) .collect::>() @@ -399,7 +405,8 @@ impl MailboxView { // 1. Collecting all the possible flags in the mailbox // 1.a Fetch them from our index - let mut known_flags: Vec = self.0 + let mut known_flags: Vec = self + .0 .snapshot .idx_by_flag .flags() @@ -440,7 +447,8 @@ impl MailboxView { pub(crate) fn unseen_count(&self) -> usize { let total = self.0.snapshot.table.len(); - let seen = self.0 + let seen = self + .0 .snapshot .idx_by_flag .get(&Flag::Seen.to_string()) @@ -462,12 +470,12 @@ mod tests { use std::fs; use crate::cryptoblob; + use crate::imap::index::MailIndex; use crate::imap::mail_view::MailView; use crate::imap::mime_view; - use crate::imap::index::MailIndex; use crate::mail::mailbox::MailMeta; - use crate::mail::unique_ident; use crate::mail::query::QueryResult; + use crate::mail::unique_ident; #[test] fn mailview_body_ext() -> Result<()> { diff --git a/src/imap/search.rs b/src/imap/search.rs index ef89288..b3c6b05 100644 --- a/src/imap/search.rs +++ b/src/imap/search.rs @@ -76,7 +76,8 @@ impl<'a> Criteria<'a> { use SearchKey::*; match self.0 { // 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 Before(_) | On(_) | Since(_) => true, // Message size is also stored in MailMeta diff --git a/src/mail/mod.rs b/src/mail/mod.rs index 7371b53..1836052 100644 --- a/src/mail/mod.rs +++ b/src/mail/mod.rs @@ -3,8 +3,8 @@ use std::io::Write; pub mod incoming; pub mod mailbox; -pub mod snapshot; pub mod query; +pub mod snapshot; pub mod uidindex; pub mod unique_ident; pub mod user; diff --git a/src/mail/query.rs b/src/mail/query.rs index 7b26cb9..8de73e6 100644 --- a/src/mail/query.rs +++ b/src/mail/query.rs @@ -1,13 +1,13 @@ -use anyhow::{Result, anyhow}; use super::mailbox::MailMeta; use super::snapshot::FrozenMailbox; -use super::unique_ident::UniqueIdent; use super::uidindex::IndexEntry; +use super::unique_ident::UniqueIdent; +use anyhow::{anyhow, Result}; use futures::stream::{FuturesUnordered, StreamExt}; /// Query is in charge of fetching efficiently /// requested data for a list of emails -pub struct Query<'a,'b> { +pub struct Query<'a, 'b> { pub frozen: &'a FrozenMailbox, pub emails: &'b [UniqueIdent], pub scope: QueryScope, @@ -20,7 +20,7 @@ pub enum QueryScope { Full, } -impl<'a,'b> Query<'a,'b> { +impl<'a, 'b> Query<'a, 'b> { pub async fn fetch(&self) -> Result>> { match self.scope { QueryScope::Index => self.index(), @@ -32,12 +32,10 @@ impl<'a,'b> Query<'a,'b> { // --- functions below are private *for reasons* fn index(&self) -> Result>> { - self - .emails + self.emails .iter() .map(|uuid| { - self - .frozen + self.frozen .snapshot .table .get(uuid) @@ -52,7 +50,11 @@ impl<'a,'b> Query<'a,'b> { let result = meta .into_iter() .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::>(); Ok(result) } @@ -65,11 +67,18 @@ impl<'a,'b> Query<'a,'b> { let meta_list = self.partial().await?; meta_list .into_iter() - .map(|meta| async move { - let content = self.frozen.mailbox.fetch_full( - *meta.uuid(), - &meta.metadata().expect("meta to be PartialResult").message_key - ).await?; + .map(|meta| async move { + let content = self + .frozen + .mailbox + .fetch_full( + *meta.uuid(), + &meta + .metadata() + .expect("meta to be PartialResult") + .message_key, + ) + .await?; Ok(meta.into_full(content).expect("meta to be PartialResult")) }) @@ -96,7 +105,7 @@ pub enum QueryResult<'a> { index: &'a IndexEntry, metadata: MailMeta, content: Vec, - } + }, } impl<'a> QueryResult<'a> { pub fn uuid(&self) -> &UniqueIdent { @@ -134,14 +143,27 @@ impl<'a> QueryResult<'a> { fn into_partial(self, metadata: MailMeta) -> Option { match self { - Self::IndexResult { uuid, index } => Some(Self::PartialResult { uuid, index, metadata }), + Self::IndexResult { uuid, index } => Some(Self::PartialResult { + uuid, + index, + metadata, + }), _ => None, } } fn into_full(self, content: Vec) -> Option { 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, } } diff --git a/src/mail/snapshot.rs b/src/mail/snapshot.rs index c3145b4..0834f09 100644 --- a/src/mail/snapshot.rs +++ b/src/mail/snapshot.rs @@ -3,16 +3,16 @@ use std::sync::Arc; use anyhow::Result; use super::mailbox::Mailbox; +use super::query::{Query, QueryScope}; use super::uidindex::UidIndex; use super::unique_ident::UniqueIdent; -use super::query::{Query, QueryScope}; /// A Frozen Mailbox has a snapshot of the current mailbox /// state that is desynchronized with the real mailbox state. /// It's up to the user to choose when their snapshot must be updated /// to give useful information to their clients /// -/// +/// pub struct FrozenMailbox { pub mailbox: Arc, pub snapshot: UidIndex, @@ -46,17 +46,17 @@ impl FrozenMailbox { /// Update the FrozenMailbox local snapshot. /// Returns the old snapshot, so you can build a diff pub async fn update(&mut self) -> UidIndex { - let old_snapshot = self.snapshot.clone(); - self.snapshot = self.mailbox.current_uid_index().await; + let old_snapshot = self.snapshot.clone(); + self.snapshot = self.mailbox.current_uid_index().await; - old_snapshot + old_snapshot } pub fn query<'a, 'b>(&'a self, uuids: &'b [UniqueIdent], scope: QueryScope) -> Query<'a, 'b> { Query { - frozen: self, - emails: uuids, - scope, + frozen: self, + emails: uuids, + scope, } } }