diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 378564b..f2d7c21 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -106,8 +106,6 @@ impl<'a> StateContext<'a> { let sum = mb.summary().await?; tracing::trace!(summary=%sum, "mailbox.summary"); - let body = vec![Data::Exists(sum.exists.try_into()?), Data::Recent(0)]; - let r_unseen = Status::ok( None, Some(Code::Unseen( @@ -118,33 +116,47 @@ impl<'a> StateContext<'a> { .map_err(Error::msg)?; //let r_permanentflags = Status::ok(None, Some(Code:: - let tr = flow::Transition::Select(mb); + let mut res = Vec::::new(); - Ok(( - vec![ - ImapRes::Data(Data::Exists(0)), - ImapRes::Data(Data::Recent(0)), - ImapRes::Data(Data::Flags(vec![])), - ImapRes::Status( - Status::ok( - None, - Some(Code::UidValidity(sum.validity)), - "UIDs valid" - ) - .map_err(Error::msg)?, - ), - /*ImapRes::Status(), - ImapRes::Status(),*/ - ImapRes::Status( - Status::ok( - Some(self.tag.clone()), - Some(Code::ReadWrite), - "Select completed", - ) - .map_err(Error::msg)?, - ), - ], - tr, - )) + res.push(ImapRes::Data(Data::Exists(sum.exists))); + + res.push(ImapRes::Data(Data::Recent(sum.recent))); + + res.push(ImapRes::Data(Data::Flags(vec![]))); + + let uid_validity = Status::ok( + None, + Some(Code::UidValidity(sum.validity)), + "UIDs valid" + ) + .map_err(Error::msg)?; + res.push(ImapRes::Status(uid_validity)); + + let next_uid = Status::ok( + None, + Some(Code::UidNext(sum.next)), + "Predict next UID" + ).map_err(Error::msg)?; + res.push(ImapRes::Status(next_uid)); + + if let Some(unseen) = sum.unseen { + let status_unseen = Status::ok( + None, + Some(Code::Unseen(unseen.clone())), + "First unseen UID", + ) + .map_err(Error::msg)?; + res.push(ImapRes::Status(status_unseen)); + } + + let last = Status::ok( + Some(self.tag.clone()), + Some(Code::ReadWrite), + "Select completed", + ).map_err(Error::msg)?; + res.push(ImapRes::Status(last)); + + let tr = flow::Transition::Select(mb); + Ok((res, tr)) } } diff --git a/src/mailbox.rs b/src/mailbox.rs index 54c22e3..b8921ec 100644 --- a/src/mailbox.rs +++ b/src/mailbox.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use anyhow::Result; use k2v_client::K2vClient; use rusoto_s3::S3Client; @@ -8,12 +10,14 @@ use crate::login::Credentials; use crate::mail_ident::*; use crate::uidindex::*; -pub struct Summary { +pub struct Summary<'a> { pub validity: ImapUidvalidity, pub next: ImapUid, - pub exists: usize, + pub exists: u32, + pub recent: u32, + pub unseen: Option<&'a ImapUid>, } -impl std::fmt::Display for Summary { +impl std::fmt::Display for Summary<'_> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, @@ -23,6 +27,9 @@ impl std::fmt::Display for Summary { } } + +// Non standard but common flags: +// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml pub struct Mailbox { bucket: String, pub name: String, @@ -34,6 +41,8 @@ pub struct Mailbox { uid_index: Bayou, } +// IDEA: We store a specific flag named $unseen. +// If it is not present, we add the virtual flag \Seen impl Mailbox { pub fn new(creds: &Credentials, name: String) -> Result { let uid_index = Bayou::::new(creds, name.clone())?; @@ -52,10 +61,15 @@ impl Mailbox { self.uid_index.sync().await?; let state = self.uid_index.state(); + let unseen = state.idx_by_flag.get(&"$unseen".to_string()).and_then(|os| os.get_min()); + let recent = state.idx_by_flag.get(&"\\Recent".to_string()).map(|os| os.len()).unwrap_or(0); + return Ok(Summary { validity: state.uidvalidity, next: state.uidnext, - exists: state.idx_by_uid.len(), + exists: u32::try_from(state.idx_by_uid.len())?, + recent: u32::try_from(recent)?, + unseen, }); } diff --git a/src/uidindex.rs b/src/uidindex.rs index f53d770..9b85238 100644 --- a/src/uidindex.rs +++ b/src/uidindex.rs @@ -182,6 +182,9 @@ impl FlagIndex { self.0.get_mut(flag).and_then(|set| set.remove(&uid)); }); } + pub fn get(&self, f: &Flag) -> Option<&OrdSet> { + self.0.get(f) + } } // ---- CUSTOM SERIALIZATION AND DESERIALIZATION ----