Implement STATUS
This commit is contained in:
parent
fc2e25ce76
commit
9b7d999fd5
4 changed files with 127 additions and 43 deletions
|
@ -2,11 +2,12 @@ use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use boitalettres::proto::res::body::Data as Body;
|
||||||
use boitalettres::proto::{Request, Response};
|
use boitalettres::proto::{Request, Response};
|
||||||
use imap_codec::types::command::{CommandBody, StatusAttribute};
|
use imap_codec::types::command::{CommandBody, StatusAttribute};
|
||||||
use imap_codec::types::flag::FlagNameAttribute;
|
use imap_codec::types::flag::FlagNameAttribute;
|
||||||
use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
|
use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
|
||||||
use imap_codec::types::response::{Code, Data};
|
use imap_codec::types::response::{Code, Data, StatusAttributeValue};
|
||||||
|
|
||||||
use crate::imap::command::anonymous;
|
use crate::imap::command::anonymous;
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
|
@ -165,26 +166,78 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
async fn status(
|
async fn status(
|
||||||
self,
|
self,
|
||||||
mailbox: &MailboxCodec,
|
mailbox: &MailboxCodec,
|
||||||
_attributes: &[StatusAttribute],
|
attributes: &[StatusAttribute],
|
||||||
) -> Result<(Response, flow::Transition)> {
|
) -> Result<(Response, flow::Transition)> {
|
||||||
let _name = String::try_from(mailbox.clone())?;
|
let name = String::try_from(mailbox.clone())?;
|
||||||
|
let mb_opt = self.user.open_mailbox(&name).await?;
|
||||||
|
let mb = match mb_opt {
|
||||||
|
Some(mb) => mb,
|
||||||
|
None => {
|
||||||
|
return Ok((
|
||||||
|
Response::no("Mailbox does not exist")?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok((Response::bad("Not implemented")?, flow::Transition::None))
|
let (view, _data) = MailboxView::new(mb).await?;
|
||||||
|
|
||||||
|
let mut ret_attrs = vec![];
|
||||||
|
for attr in attributes.iter() {
|
||||||
|
ret_attrs.push(match attr {
|
||||||
|
StatusAttribute::Messages => StatusAttributeValue::Messages(view.exists()?),
|
||||||
|
StatusAttribute::Unseen => {
|
||||||
|
StatusAttributeValue::Unseen(view.unseen().map(|x| x.get()).unwrap_or(0))
|
||||||
|
}
|
||||||
|
StatusAttribute::Recent => StatusAttributeValue::Recent(view.recent()?),
|
||||||
|
StatusAttribute::UidNext => StatusAttributeValue::UidNext(view.uidnext()),
|
||||||
|
StatusAttribute::UidValidity => {
|
||||||
|
StatusAttributeValue::UidValidity(view.uidvalidity())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = vec![Body::Data(Data::Status {
|
||||||
|
mailbox: mailbox.clone(),
|
||||||
|
attributes: ret_attrs,
|
||||||
|
})];
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Response::ok("STATUS completed")?.with_body(data),
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn subscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
|
async fn subscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
|
||||||
let _name = String::try_from(mailbox.clone())?;
|
let name = String::try_from(mailbox.clone())?;
|
||||||
|
|
||||||
Ok((Response::bad("Not implemented")?, flow::Transition::None))
|
if self.user.has_mailbox(&name).await? {
|
||||||
|
Ok((Response::ok("SUBSCRIBE complete")?, flow::Transition::None))
|
||||||
|
} else {
|
||||||
|
Ok((
|
||||||
|
Response::bad(&format!("Mailbox {} does not exist", name))?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unsubscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
|
async fn unsubscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
|
||||||
let _name = String::try_from(mailbox.clone())?;
|
let name = String::try_from(mailbox.clone())?;
|
||||||
|
|
||||||
|
if self.user.has_mailbox(&name).await? {
|
||||||
Ok((
|
Ok((
|
||||||
Response::bad("Aerogramme does not support unsubscribing from a mailbox")?,
|
Response::bad(&format!(
|
||||||
|
"Cannot unsubscribe from mailbox {}: not supported by Aerogramme",
|
||||||
|
name
|
||||||
|
))?,
|
||||||
flow::Transition::None,
|
flow::Transition::None,
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
Ok((
|
||||||
|
Response::bad(&format!("Mailbox {} does not exist", name))?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -19,7 +19,7 @@ use imap_codec::types::sequence::{self, SequenceSet};
|
||||||
use mail_parser::*;
|
use mail_parser::*;
|
||||||
|
|
||||||
use crate::mail::mailbox::Mailbox;
|
use crate::mail::mailbox::Mailbox;
|
||||||
use crate::mail::uidindex::UidIndex;
|
use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex};
|
||||||
|
|
||||||
const DEFAULT_FLAGS: [Flag; 5] = [
|
const DEFAULT_FLAGS: [Flag; 5] = [
|
||||||
Flag::Seen,
|
Flag::Seen,
|
||||||
|
@ -55,12 +55,12 @@ impl MailboxView {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut data = Vec::<Body>::new();
|
let mut data = Vec::<Body>::new();
|
||||||
data.push(new_view.exists()?);
|
data.push(new_view.exists_status()?);
|
||||||
data.push(new_view.recent()?);
|
data.push(new_view.recent_status()?);
|
||||||
data.extend(new_view.flags()?.into_iter());
|
data.extend(new_view.flags_status()?.into_iter());
|
||||||
data.push(new_view.uidvalidity()?);
|
data.push(new_view.uidvalidity_status()?);
|
||||||
data.push(new_view.uidnext()?);
|
data.push(new_view.uidnext_status()?);
|
||||||
if let Some(unseen) = new_view.unseen()? {
|
if let Some(unseen) = new_view.unseen_status()? {
|
||||||
data.push(unseen);
|
data.push(unseen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +87,9 @@ impl MailboxView {
|
||||||
|
|
||||||
if new_view.known_state.uidvalidity != self.known_state.uidvalidity {
|
if new_view.known_state.uidvalidity != self.known_state.uidvalidity {
|
||||||
// TODO: do we want to push less/more info than this?
|
// TODO: do we want to push less/more info than this?
|
||||||
data.push(new_view.uidvalidity()?);
|
data.push(new_view.uidvalidity_status()?);
|
||||||
data.push(new_view.exists()?);
|
data.push(new_view.exists_status()?);
|
||||||
data.push(new_view.uidnext()?);
|
data.push(new_view.uidnext_status()?);
|
||||||
} else {
|
} else {
|
||||||
// Calculate diff between two mailbox states
|
// Calculate diff between two mailbox states
|
||||||
// See example in IMAP RFC in section on NOOP command:
|
// See example in IMAP RFC in section on NOOP command:
|
||||||
|
@ -117,7 +117,7 @@ impl MailboxView {
|
||||||
|
|
||||||
// - if new mails arrived, notify client of number of existing mails
|
// - 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 {
|
if new_view.known_state.table.len() != self.known_state.table.len() - n_expunge {
|
||||||
data.push(new_view.exists()?);
|
data.push(new_view.exists_status()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// - if flags changed for existing mails, tell client
|
// - if flags changed for existing mails, tell client
|
||||||
|
@ -315,37 +315,39 @@ impl MailboxView {
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
|
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
|
||||||
fn uidvalidity(&self) -> Result<Body> {
|
fn uidvalidity_status(&self) -> Result<Body> {
|
||||||
let uid_validity = Status::ok(
|
let uid_validity = Status::ok(
|
||||||
None,
|
None,
|
||||||
Some(Code::UidValidity(self.known_state.uidvalidity)),
|
Some(Code::UidValidity(self.uidvalidity())),
|
||||||
"UIDs valid",
|
"UIDs valid",
|
||||||
)
|
)
|
||||||
.map_err(Error::msg)?;
|
.map_err(Error::msg)?;
|
||||||
Ok(Body::Status(uid_validity))
|
Ok(Body::Status(uid_validity))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn uidvalidity(&self) -> ImapUidvalidity {
|
||||||
|
self.known_state.uidvalidity
|
||||||
|
}
|
||||||
|
|
||||||
/// Produce an OK [UIDNEXT _] message corresponding to `known_state`
|
/// Produce an OK [UIDNEXT _] message corresponding to `known_state`
|
||||||
fn uidnext(&self) -> Result<Body> {
|
fn uidnext_status(&self) -> Result<Body> {
|
||||||
let next_uid = Status::ok(
|
let next_uid = Status::ok(
|
||||||
None,
|
None,
|
||||||
Some(Code::UidNext(self.known_state.uidnext)),
|
Some(Code::UidNext(self.uidnext())),
|
||||||
"Predict next UID",
|
"Predict next UID",
|
||||||
)
|
)
|
||||||
.map_err(Error::msg)?;
|
.map_err(Error::msg)?;
|
||||||
Ok(Body::Status(next_uid))
|
Ok(Body::Status(next_uid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn uidnext(&self) -> ImapUid {
|
||||||
|
self.known_state.uidnext
|
||||||
|
}
|
||||||
|
|
||||||
/// Produces an UNSEEN message (if relevant) corresponding to the
|
/// Produces an UNSEEN message (if relevant) corresponding to the
|
||||||
/// first unseen message id in `known_state`
|
/// first unseen message id in `known_state`
|
||||||
fn unseen(&self) -> Result<Option<Body>> {
|
fn unseen_status(&self) -> Result<Option<Body>> {
|
||||||
let unseen = self
|
if let Some(unseen) = self.unseen() {
|
||||||
.known_state
|
|
||||||
.idx_by_flag
|
|
||||||
.get(&"$unseen".to_string())
|
|
||||||
.and_then(|os| os.get_min())
|
|
||||||
.cloned();
|
|
||||||
if let Some(unseen) = unseen {
|
|
||||||
let status_unseen =
|
let status_unseen =
|
||||||
Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID")
|
Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID")
|
||||||
.map_err(Error::msg)?;
|
.map_err(Error::msg)?;
|
||||||
|
@ -355,29 +357,43 @@ impl MailboxView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unseen(&self) -> Option<ImapUid> {
|
||||||
|
self.known_state
|
||||||
|
.idx_by_flag
|
||||||
|
.get(&"$unseen".to_string())
|
||||||
|
.and_then(|os| os.get_min())
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Produce an EXISTS message corresponding to the number of mails
|
/// Produce an EXISTS message corresponding to the number of mails
|
||||||
/// in `known_state`
|
/// in `known_state`
|
||||||
fn exists(&self) -> Result<Body> {
|
fn exists_status(&self) -> Result<Body> {
|
||||||
let exists = u32::try_from(self.known_state.idx_by_uid.len())?;
|
Ok(Body::Data(Data::Exists(self.exists()?)))
|
||||||
Ok(Body::Data(Data::Exists(exists)))
|
}
|
||||||
|
|
||||||
|
pub(crate) fn exists(&self) -> Result<u32> {
|
||||||
|
Ok(u32::try_from(self.known_state.idx_by_uid.len())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a RECENT message corresponding to the number of
|
/// Produce a RECENT message corresponding to the number of
|
||||||
/// recent mails in `known_state`
|
/// recent mails in `known_state`
|
||||||
fn recent(&self) -> Result<Body> {
|
fn recent_status(&self) -> Result<Body> {
|
||||||
|
Ok(Body::Data(Data::Recent(self.recent()?)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn recent(&self) -> Result<u32> {
|
||||||
let recent = self
|
let recent = self
|
||||||
.known_state
|
.known_state
|
||||||
.idx_by_flag
|
.idx_by_flag
|
||||||
.get(&"\\Recent".to_string())
|
.get(&"\\Recent".to_string())
|
||||||
.map(|os| os.len())
|
.map(|os| os.len())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let recent = u32::try_from(recent)?;
|
Ok(u32::try_from(recent)?)
|
||||||
Ok(Body::Data(Data::Recent(recent)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a FLAGS and a PERMANENTFLAGS message that indicates
|
/// Produce a FLAGS and a PERMANENTFLAGS message that indicates
|
||||||
/// the flags that are in `known_state` + default flags
|
/// the flags that are in `known_state` + default flags
|
||||||
fn flags(&self) -> Result<Vec<Body>> {
|
fn flags_status(&self) -> Result<Vec<Body>> {
|
||||||
let mut flags: Vec<Flag> = self
|
let mut flags: Vec<Flag> = self
|
||||||
.known_state
|
.known_state
|
||||||
.idx_by_flag
|
.idx_by_flag
|
||||||
|
|
|
@ -16,7 +16,7 @@ use crate::time::now_msec;
|
||||||
/// required by Aerogramme.
|
/// required by Aerogramme.
|
||||||
/// Their main property is to be unique without having to rely
|
/// Their main property is to be unique without having to rely
|
||||||
/// on synchronization between IMAP processes.
|
/// on synchronization between IMAP processes.
|
||||||
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||||
pub struct UniqueIdent(pub [u8; 24]);
|
pub struct UniqueIdent(pub [u8; 24]);
|
||||||
|
|
||||||
struct IdentGenerator {
|
struct IdentGenerator {
|
||||||
|
@ -78,6 +78,12 @@ impl std::fmt::Display for UniqueIdent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for UniqueIdent {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", hex::encode(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for UniqueIdent {
|
impl FromStr for UniqueIdent {
|
||||||
type Err = &'static str;
|
type Err = &'static str;
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,10 @@ impl User {
|
||||||
/// Opens an existing mailbox given its IMAP name.
|
/// Opens an existing mailbox given its IMAP name.
|
||||||
pub async fn open_mailbox(&self, name: &str) -> Result<Option<Arc<Mailbox>>> {
|
pub async fn open_mailbox(&self, name: &str) -> Result<Option<Arc<Mailbox>>> {
|
||||||
let (mut list, ct) = self.load_mailbox_list().await?;
|
let (mut list, ct) = self.load_mailbox_list().await?;
|
||||||
eprintln!("List of mailboxes: {:?}", &list.0);
|
eprintln!("List of mailboxes:");
|
||||||
|
for ent in list.0.iter() {
|
||||||
|
eprintln!(" - {:?}", ent);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((uidvalidity, Some(mbid))) = list.get_mailbox(name) {
|
if let Some((uidvalidity, Some(mbid))) = list.get_mailbox(name) {
|
||||||
let mb_opt = self.open_mailbox_by_id(mbid, uidvalidity).await?;
|
let mb_opt = self.open_mailbox_by_id(mbid, uidvalidity).await?;
|
||||||
|
@ -88,6 +91,12 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether mailbox exists
|
||||||
|
pub async fn has_mailbox(&self, name: &str) -> Result<bool> {
|
||||||
|
let (list, _ct) = self.load_mailbox_list().await?;
|
||||||
|
Ok(list.has_mailbox(name))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new mailbox in the user's IMAP namespace.
|
/// Creates a new mailbox in the user's IMAP namespace.
|
||||||
pub async fn create_mailbox(&self, name: &str) -> Result<()> {
|
pub async fn create_mailbox(&self, name: &str) -> Result<()> {
|
||||||
if name.ends_with(MAILBOX_HIERARCHY_DELIMITER) {
|
if name.ends_with(MAILBOX_HIERARCHY_DELIMITER) {
|
||||||
|
|
Loading…
Reference in a new issue