Implement IDLE in selected state
This commit is contained in:
parent
733c59ed80
commit
a8d0e4a994
4 changed files with 106 additions and 17 deletions
|
@ -21,10 +21,7 @@ pub async fn dispatch<'a>(ctx: AnonymousContext<'a>) -> Result<(Response, flow::
|
|||
CommandBody::Capability => ctx.capability().await,
|
||||
CommandBody::Logout => ctx.logout().await,
|
||||
CommandBody::Login { username, password } => ctx.login(username, password).await,
|
||||
_ => Ok((
|
||||
Response::no("This command is not available in the ANONYMOUS state.")?,
|
||||
flow::Transition::None,
|
||||
)),
|
||||
_ => Ok((Response::no("Command unavailable")?, flow::Transition::None)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ pub struct SelectedContext<'a> {
|
|||
|
||||
pub async fn dispatch<'a>(ctx: SelectedContext<'a>) -> Result<(Response, flow::Transition)> {
|
||||
match &ctx.req.command.body {
|
||||
CommandBody::Noop => ctx.noop().await,
|
||||
CommandBody::Fetch {
|
||||
sequence_set,
|
||||
attributes,
|
||||
|
@ -47,4 +48,12 @@ impl<'a> SelectedContext<'a> {
|
|||
) -> Result<(Response, flow::Transition)> {
|
||||
Ok((Response::bad("Not implemented")?, flow::Transition::None))
|
||||
}
|
||||
|
||||
pub async fn noop(self) -> Result<(Response, flow::Transition)> {
|
||||
let updates = self.mailbox.update().await?;
|
||||
Ok((
|
||||
Response::ok("Noop completed.")?.with_body(updates),
|
||||
flow::Transition::None,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use std::num::NonZeroU32;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use boitalettres::proto::res::body::Data as Body;
|
||||
use imap_codec::types::core::Atom;
|
||||
use imap_codec::types::flag::Flag;
|
||||
use imap_codec::types::response::{Code, Data, Status};
|
||||
use imap_codec::types::response::{Code, Data, MessageAttribute, Status};
|
||||
|
||||
use crate::mail::mailbox::Mailbox;
|
||||
use crate::mail::uidindex::UidIndex;
|
||||
|
@ -58,6 +59,79 @@ impl MailboxView {
|
|||
Ok((new_view, data))
|
||||
}
|
||||
|
||||
/// Looks up state changes in the mailbox and produces a set of IMAP
|
||||
/// responses describing the new state.
|
||||
pub async fn update(&mut self) -> Result<Vec<Body>> {
|
||||
self.mailbox.sync().await?;
|
||||
// TODO THIS IS JUST A TEST REMOVE LATER
|
||||
self.mailbox.test().await?;
|
||||
|
||||
let new_view = MailboxView {
|
||||
mailbox: self.mailbox.clone(),
|
||||
known_state: self.mailbox.current_uid_index().await,
|
||||
};
|
||||
|
||||
let mut data = Vec::<Body>::new();
|
||||
|
||||
if new_view.known_state.uidvalidity != self.known_state.uidvalidity {
|
||||
// TODO: do we want to push less/more info than this?
|
||||
data.push(new_view.uidvalidity()?);
|
||||
data.push(new_view.exists()?);
|
||||
data.push(new_view.uidnext()?);
|
||||
} else {
|
||||
// Calculate diff between two mailbox states
|
||||
// See example in IMAP RFC in section on NOOP command:
|
||||
// we want to produce something like this:
|
||||
// C: a047 NOOP
|
||||
// S: * 22 EXPUNGE
|
||||
// S: * 23 EXISTS
|
||||
// S: * 14 FETCH (UID 1305 FLAGS (\Seen \Deleted))
|
||||
// S: a047 OK Noop completed
|
||||
// In other words:
|
||||
// - notify client of expunged mails
|
||||
// - if new mails arrived, notify client of number of existing mails
|
||||
// - if flags changed for existing mails, tell client
|
||||
|
||||
// - notify client of expunged mails
|
||||
let mut n_expunge = 0;
|
||||
for (i, (uid, uuid)) in self.known_state.idx_by_uid.iter().enumerate() {
|
||||
if !new_view.known_state.table.contains_key(uuid) {
|
||||
data.push(Body::Data(Data::Expunge(
|
||||
NonZeroU32::try_from((i + 1 - n_expunge) as u32).unwrap(),
|
||||
)));
|
||||
n_expunge += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// - if new mails arrived, notify client of number of existing mails
|
||||
if new_view.known_state.table.len() != self.known_state.table.len() - n_expunge {
|
||||
data.push(new_view.exists()?);
|
||||
}
|
||||
|
||||
// - if flags changed for existing mails, tell client
|
||||
for (i, (uid, uuid)) in new_view.known_state.idx_by_uid.iter().enumerate() {
|
||||
let old_mail = self.known_state.table.get(uuid);
|
||||
let new_mail = new_view.known_state.table.get(uuid);
|
||||
if old_mail.is_some() && old_mail != new_mail {
|
||||
if let Some((uid, flags)) = new_mail {
|
||||
data.push(Body::Data(Data::Fetch {
|
||||
seq_or_uid: NonZeroU32::try_from((i + 1) as u32).unwrap(),
|
||||
attributes: vec![
|
||||
MessageAttribute::Uid((*uid).try_into().unwrap()),
|
||||
MessageAttribute::Flags(
|
||||
flags.iter().filter_map(|f| string_to_flag(f)).collect(),
|
||||
),
|
||||
],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*self = new_view;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
|
||||
|
@ -128,18 +202,7 @@ impl MailboxView {
|
|||
.known_state
|
||||
.idx_by_flag
|
||||
.flags()
|
||||
.map(|f| match f.chars().next() {
|
||||
Some('\\') => None,
|
||||
Some('$') if f == "$unseen" => None,
|
||||
Some(_) => match Atom::try_from(f.clone()) {
|
||||
Err(_) => {
|
||||
tracing::error!(flag=%f, "Unable to encode flag as IMAP atom");
|
||||
None
|
||||
}
|
||||
Ok(a) => Some(Flag::Keyword(a)),
|
||||
},
|
||||
None => None,
|
||||
})
|
||||
.map(|f| string_to_flag(f))
|
||||
.flatten()
|
||||
.collect();
|
||||
for f in DEFAULT_FLAGS.iter() {
|
||||
|
@ -158,3 +221,18 @@ impl MailboxView {
|
|||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_flag(f: &str) -> Option<Flag> {
|
||||
match f.chars().next() {
|
||||
Some('\\') => None,
|
||||
Some('$') if f == "$unseen" => None,
|
||||
Some(_) => match Atom::try_from(f.clone()) {
|
||||
Err(_) => {
|
||||
tracing::error!(flag=%f, "Unable to encode flag as IMAP atom");
|
||||
None
|
||||
}
|
||||
Ok(a) => Some(Flag::Keyword(a)),
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,11 @@ impl Mailbox {
|
|||
Ok(Self { id, mbox })
|
||||
}
|
||||
|
||||
/// Sync data with backing store
|
||||
pub async fn sync(&self) -> Result<()> {
|
||||
self.mbox.write().await.uid_index.sync().await
|
||||
}
|
||||
|
||||
/// Get a clone of the current UID Index of this mailbox
|
||||
/// (cloning is cheap so don't hesitate to use this)
|
||||
pub async fn current_uid_index(&self) -> UidIndex {
|
||||
|
|
Loading…
Reference in a new issue