CONDSTORE #71
5 changed files with 58 additions and 35 deletions
|
@ -48,15 +48,21 @@ impl ServerCapability {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ClientStatus {
|
pub enum ClientStatus {
|
||||||
NotSupportedByServer,
|
NotSupportedByServer,
|
||||||
Disabled,
|
Disabled,
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
impl ClientStatus {
|
||||||
|
pub fn is_enabled(&self) -> bool {
|
||||||
|
matches!(self, Self::Enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct ClientCapability {
|
pub struct ClientCapability {
|
||||||
condstore: ClientStatus,
|
pub condstore: ClientStatus,
|
||||||
utf8kind: Option<Utf8Kind>,
|
pub utf8kind: Option<Utf8Kind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientCapability {
|
impl ClientCapability {
|
||||||
|
|
|
@ -292,7 +292,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let view = MailboxView::new(mb).await;
|
let view = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
|
||||||
|
|
||||||
let mut ret_attrs = vec![];
|
let mut ret_attrs = vec![];
|
||||||
for attr in attributes.iter() {
|
for attr in attributes.iter() {
|
||||||
|
@ -439,7 +439,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
};
|
};
|
||||||
tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.selected");
|
tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.selected");
|
||||||
|
|
||||||
let mb = MailboxView::new(mb).await;
|
let mb = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
|
||||||
let data = mb.summary()?;
|
let data = mb.summary()?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -474,7 +474,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
};
|
};
|
||||||
tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.examined");
|
tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.examined");
|
||||||
|
|
||||||
let mb = MailboxView::new(mb).await;
|
let mb = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
|
||||||
let data = mb.summary()?;
|
let data = mb.summary()?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
|
|
@ -127,7 +127,7 @@ impl<'a> ExaminedContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
|
pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
self.mailbox.0.mailbox.force_sync().await?;
|
self.mailbox.internal.mailbox.force_sync().await?;
|
||||||
|
|
||||||
let updates = self.mailbox.update().await?;
|
let updates = self.mailbox.update().await?;
|
||||||
Ok((
|
Ok((
|
||||||
|
|
|
@ -152,7 +152,7 @@ impl<'a> SelectedContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
|
pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
self.mailbox.0.mailbox.force_sync().await?;
|
self.mailbox.internal.mailbox.force_sync().await?;
|
||||||
|
|
||||||
let updates = self.mailbox.update().await?;
|
let updates = self.mailbox.update().await?;
|
||||||
Ok((
|
Ok((
|
||||||
|
|
|
@ -8,7 +8,7 @@ use futures::stream::{FuturesOrdered, StreamExt};
|
||||||
use imap_codec::imap_types::core::Charset;
|
use imap_codec::imap_types::core::Charset;
|
||||||
use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItem};
|
use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItem};
|
||||||
use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType};
|
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::response::{Code, CodeOther, Data, Status};
|
||||||
use imap_codec::imap_types::search::SearchKey;
|
use imap_codec::imap_types::search::SearchKey;
|
||||||
use imap_codec::imap_types::sequence::SequenceSet;
|
use imap_codec::imap_types::sequence::SequenceSet;
|
||||||
|
|
||||||
|
@ -39,12 +39,18 @@ 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 internal: FrozenMailbox,
|
||||||
|
pub is_condstore: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl MailboxView {
|
impl MailboxView {
|
||||||
/// Creates a new IMAP view into a mailbox.
|
/// Creates a new IMAP view into a mailbox.
|
||||||
pub async fn new(mailbox: Arc<Mailbox>) -> Self {
|
pub async fn new(mailbox: Arc<Mailbox>, is_cond: bool) -> Self {
|
||||||
Self(mailbox.frozen().await)
|
Self {
|
||||||
|
internal: mailbox.frozen().await,
|
||||||
|
is_condstore: is_cond,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an updated view, useful to make a diff
|
/// Create an updated view, useful to make a diff
|
||||||
|
@ -54,8 +60,8 @@ impl MailboxView {
|
||||||
/// This does NOT trigger a sync, it bases itself on what is currently
|
/// This does NOT trigger a sync, it bases itself on what is currently
|
||||||
/// loaded in RAM by Bayou.
|
/// loaded in RAM by Bayou.
|
||||||
pub async fn update(&mut self) -> Result<Vec<Body<'static>>> {
|
pub async fn update(&mut self) -> Result<Vec<Body<'static>>> {
|
||||||
let old_snapshot = self.0.update().await;
|
let old_snapshot = self.internal.update().await;
|
||||||
let new_snapshot = &self.0.snapshot;
|
let new_snapshot = &self.internal.snapshot;
|
||||||
|
|
||||||
let mut data = Vec::<Body>::new();
|
let mut data = Vec::<Body>::new();
|
||||||
|
|
||||||
|
@ -130,6 +136,9 @@ impl MailboxView {
|
||||||
data.extend(self.flags_status()?.into_iter());
|
data.extend(self.flags_status()?.into_iter());
|
||||||
data.push(self.uidvalidity_status()?);
|
data.push(self.uidvalidity_status()?);
|
||||||
data.push(self.uidnext_status()?);
|
data.push(self.uidnext_status()?);
|
||||||
|
if self.is_condstore {
|
||||||
|
data.push(self.highestmodseq_status()?);
|
||||||
|
}
|
||||||
/*self.unseen_first_status()?
|
/*self.unseen_first_status()?
|
||||||
.map(|unseen_status| data.push(unseen_status));*/
|
.map(|unseen_status| data.push(unseen_status));*/
|
||||||
|
|
||||||
|
@ -144,7 +153,7 @@ impl MailboxView {
|
||||||
flags: &[Flag<'a>],
|
flags: &[Flag<'a>],
|
||||||
is_uid_store: &bool,
|
is_uid_store: &bool,
|
||||||
) -> Result<Vec<Body<'static>>> {
|
) -> Result<Vec<Body<'static>>> {
|
||||||
self.0.sync().await?;
|
self.internal.sync().await?;
|
||||||
|
|
||||||
let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
|
let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -153,13 +162,13 @@ impl MailboxView {
|
||||||
for mi in mails.iter() {
|
for mi in mails.iter() {
|
||||||
match kind {
|
match kind {
|
||||||
StoreType::Add => {
|
StoreType::Add => {
|
||||||
self.0.mailbox.add_flags(mi.uuid, &flags[..]).await?;
|
self.internal.mailbox.add_flags(mi.uuid, &flags[..]).await?;
|
||||||
}
|
}
|
||||||
StoreType::Remove => {
|
StoreType::Remove => {
|
||||||
self.0.mailbox.del_flags(mi.uuid, &flags[..]).await?;
|
self.internal.mailbox.del_flags(mi.uuid, &flags[..]).await?;
|
||||||
}
|
}
|
||||||
StoreType::Replace => {
|
StoreType::Replace => {
|
||||||
self.0.mailbox.set_flags(mi.uuid, &flags[..]).await?;
|
self.internal.mailbox.set_flags(mi.uuid, &flags[..]).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,8 +178,8 @@ impl MailboxView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn expunge(&mut self) -> Result<Vec<Body<'static>>> {
|
pub async fn expunge(&mut self) -> Result<Vec<Body<'static>>> {
|
||||||
self.0.sync().await?;
|
self.internal.sync().await?;
|
||||||
let state = self.0.peek().await;
|
let state = self.internal.peek().await;
|
||||||
|
|
||||||
let deleted_flag = Flag::Deleted.to_string();
|
let deleted_flag = Flag::Deleted.to_string();
|
||||||
let msgs = state
|
let msgs = state
|
||||||
|
@ -180,7 +189,7 @@ impl MailboxView {
|
||||||
.map(|(uuid, _)| *uuid);
|
.map(|(uuid, _)| *uuid);
|
||||||
|
|
||||||
for msg in msgs {
|
for msg in msgs {
|
||||||
self.0.mailbox.delete(msg).await?;
|
self.internal.mailbox.delete(msg).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update().await
|
self.update().await
|
||||||
|
@ -197,7 +206,7 @@ impl MailboxView {
|
||||||
|
|
||||||
let mut new_uuids = vec![];
|
let mut new_uuids = vec![];
|
||||||
for mi in mails.iter() {
|
for mi in mails.iter() {
|
||||||
new_uuids.push(to.copy_from(&self.0.mailbox, mi.uuid).await?);
|
new_uuids.push(to.copy_from(&self.internal.mailbox, mi.uuid).await?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
|
@ -224,7 +233,7 @@ impl MailboxView {
|
||||||
let mails = idx.fetch(sequence_set, *is_uid_copy)?;
|
let mails = idx.fetch(sequence_set, *is_uid_copy)?;
|
||||||
|
|
||||||
for mi in mails.iter() {
|
for mi in mails.iter() {
|
||||||
to.move_from(&self.0.mailbox, mi.uuid).await?;
|
to.move_from(&self.internal.mailbox, mi.uuid).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
|
@ -268,7 +277,7 @@ impl MailboxView {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|midx| midx.uuid)
|
.map(|midx| midx.uuid)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let query_result = self.0.query(&uuids, query_scope).fetch().await?;
|
let query_result = self.internal.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
|
let views = query_result
|
||||||
|
@ -294,7 +303,7 @@ 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
|
self.internal
|
||||||
.mailbox
|
.mailbox
|
||||||
.add_flags(*mv.query_result.uuid(), &[seen_flag])
|
.add_flags(*mv.query_result.uuid(), &[seen_flag])
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -332,7 +341,7 @@ impl MailboxView {
|
||||||
// 4. Fetch additional info about the emails
|
// 4. Fetch additional info about the emails
|
||||||
let query_scope = crit.query_scope();
|
let query_scope = crit.query_scope();
|
||||||
let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::<Vec<_>>();
|
let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::<Vec<_>>();
|
||||||
let query_result = self.0.query(&uuids, query_scope).fetch().await?;
|
let query_result = self.internal.query(&uuids, query_scope).fetch().await?;
|
||||||
|
|
||||||
// 5. If needed, filter the selection based on the body
|
// 5. If needed, filter the selection based on the body
|
||||||
let kept_query = crit.filter_on_query(&to_fetch, &query_result)?;
|
let kept_query = crit.filter_on_query(&to_fetch, &query_result)?;
|
||||||
|
@ -354,7 +363,7 @@ impl MailboxView {
|
||||||
/// It's not trivial to refactor the code to do that, so we are doing
|
/// It's not trivial to refactor the code to do that, so we are doing
|
||||||
/// some useless computation for now...
|
/// some useless computation for now...
|
||||||
fn index<'a>(&'a self) -> Result<Index<'a>> {
|
fn index<'a>(&'a self) -> Result<Index<'a>> {
|
||||||
Index::new(&self.0.snapshot)
|
Index::new(&self.internal.snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
|
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
|
||||||
|
@ -369,7 +378,7 @@ impl MailboxView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn uidvalidity(&self) -> ImapUidvalidity {
|
pub(crate) fn uidvalidity(&self) -> ImapUidvalidity {
|
||||||
self.0.snapshot.uidvalidity
|
self.internal.snapshot.uidvalidity
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce an OK [UIDNEXT _] message corresponding to `known_state`
|
/// Produce an OK [UIDNEXT _] message corresponding to `known_state`
|
||||||
|
@ -384,7 +393,15 @@ impl MailboxView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn uidnext(&self) -> ImapUid {
|
pub(crate) fn uidnext(&self) -> ImapUid {
|
||||||
self.0.snapshot.uidnext
|
self.internal.snapshot.uidnext
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn highestmodseq_status(&self) -> Result<Body<'static>> {
|
||||||
|
Ok(Body::Status(Status::ok(
|
||||||
|
None,
|
||||||
|
Some(Code::Other(CodeOther::unvalidated(format!("HIGHESTMODSEQ {}", 0).into_bytes()))),
|
||||||
|
"Highest",
|
||||||
|
)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce an EXISTS message corresponding to the number of mails
|
/// Produce an EXISTS message corresponding to the number of mails
|
||||||
|
@ -394,7 +411,7 @@ impl MailboxView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn exists(&self) -> Result<u32> {
|
pub(crate) fn exists(&self) -> Result<u32> {
|
||||||
Ok(u32::try_from(self.0.snapshot.idx_by_uid.len())?)
|
Ok(u32::try_from(self.internal.snapshot.idx_by_uid.len())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a RECENT message corresponding to the number of
|
/// Produce a RECENT message corresponding to the number of
|
||||||
|
@ -416,7 +433,7 @@ impl MailboxView {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn unseen_first(&self) -> Result<Option<NonZeroU32>> {
|
fn unseen_first(&self) -> Result<Option<NonZeroU32>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.0
|
.internal
|
||||||
.snapshot
|
.snapshot
|
||||||
.table
|
.table
|
||||||
.values()
|
.values()
|
||||||
|
@ -428,7 +445,7 @@ impl MailboxView {
|
||||||
|
|
||||||
pub(crate) fn recent(&self) -> Result<u32> {
|
pub(crate) fn recent(&self) -> Result<u32> {
|
||||||
let recent = self
|
let recent = self
|
||||||
.0
|
.internal
|
||||||
.snapshot
|
.snapshot
|
||||||
.idx_by_flag
|
.idx_by_flag
|
||||||
.get(&"\\Recent".to_string())
|
.get(&"\\Recent".to_string())
|
||||||
|
@ -445,7 +462,7 @@ 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
|
let mut known_flags: Vec<Flag> = self
|
||||||
.0
|
.internal
|
||||||
.snapshot
|
.snapshot
|
||||||
.idx_by_flag
|
.idx_by_flag
|
||||||
.flags()
|
.flags()
|
||||||
|
@ -485,9 +502,9 @@ 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.internal.snapshot.table.len();
|
||||||
let seen = self
|
let seen = self
|
||||||
.0
|
.internal
|
||||||
.snapshot
|
.snapshot
|
||||||
.idx_by_flag
|
.idx_by_flag
|
||||||
.get(&Flag::Seen.to_string())
|
.get(&Flag::Seen.to_string())
|
||||||
|
|
Loading…
Reference in a new issue