Aerogramme refactoring #57
7 changed files with 120 additions and 54 deletions
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue