Add support for Gmail Labels

This commit is contained in:
Crispin Stichart 2022-04-04 20:51:40 -05:00
parent afbc5118f2
commit b22ce3a605
5 changed files with 18 additions and 85 deletions

View file

@ -22,7 +22,7 @@ native-tls = { version = "0.2.2", optional = true }
rustls-connector = { version = "0.16.1", optional = true } rustls-connector = { version = "0.16.1", optional = true }
regex = "1.0" regex = "1.0"
bufstream = "0.1.3" bufstream = "0.1.3"
imap-proto = "0.15.0" imap-proto = "0.16.0"
nom = { version = "7.1.0", default-features = false } nom = { version = "7.1.0", default-features = false }
base64 = "0.13" base64 = "0.13"
chrono = { version = "0.4", default-features = false, features = ["std"]} chrono = { version = "0.4", default-features = false, features = ["std"]}

View file

@ -417,7 +417,7 @@ mod tests {
let first = names.get(0).unwrap(); let first = names.get(0).unwrap();
assert_eq!( assert_eq!(
first.attributes(), first.attributes(),
&[NameAttribute::from("\\HasNoChildren")] &[NameAttribute::Extension(Cow::Borrowed("\\HasNoChildren"))]
); );
assert_eq!(first.delimiter(), Some(".")); assert_eq!(first.delimiter(), Some("."));
assert_eq!(first.name(), "INBOX"); assert_eq!(first.name(), "INBOX");
@ -507,7 +507,7 @@ mod tests {
let first = names.get(0).unwrap(); let first = names.get(0).unwrap();
assert_eq!( assert_eq!(
first.attributes(), first.attributes(),
&[NameAttribute::from("\\HasNoChildren")] &[NameAttribute::Extension(Cow::Borrowed("\\HasNoChildren"))]
); );
assert_eq!(first.delimiter(), Some(".")); assert_eq!(first.delimiter(), Some("."));
assert_eq!(first.name(), "INBOX"); assert_eq!(first.name(), "INBOX");

View file

@ -219,6 +219,17 @@ impl<'a> Fetch<'a> {
}) })
} }
/// Extract the `X-GM-LABELS` of a `FETCH` response
///
/// This is a Gmail-specific extension. See their
/// [developer's page](https://developers.google.com/gmail/imap/imap-extensions) for details.
pub fn gmail_labels(&'a self) -> Option<impl Iterator<Item = &'a str>> {
self.fetch.iter().find_map(|av| match av {
AttributeValue::GmailLabels(labels) => Some(labels.iter().map(|cow| cow.as_ref())),
_ => None,
})
}
/// Get an owned copy of the [`Fetch`]. /// Get an owned copy of the [`Fetch`].
pub fn into_owned(self) -> Fetch<'static> { pub fn into_owned(self) -> Fetch<'static> {
Fetch { Fetch {

View file

@ -113,7 +113,7 @@ mod mailbox;
pub use self::mailbox::Mailbox; pub use self::mailbox::Mailbox;
mod name; mod name;
pub use self::name::{Name, NameAttribute, Names}; pub use self::name::{Name, Names};
mod capabilities; mod capabilities;
pub use self::capabilities::Capabilities; pub use self::capabilities::Capabilities;

View file

@ -1,7 +1,7 @@
use crate::error::Error; use crate::error::Error;
use crate::parse::{parse_many_into, MapOrNot}; use crate::parse::{parse_many_into, MapOrNot};
use crate::types::UnsolicitedResponse; use crate::types::UnsolicitedResponse;
use imap_proto::{MailboxDatum, Response}; use imap_proto::{MailboxDatum, NameAttribute, Response};
use ouroboros::self_referencing; use ouroboros::self_referencing;
use std::borrow::Cow; use std::borrow::Cow;
use std::slice::Iter; use std::slice::Iter;
@ -28,11 +28,11 @@ impl Names {
let mut names = Vec::new(); let mut names = Vec::new();
parse_many_into(input, &mut names, unsolicited, |response| match response { parse_many_into(input, &mut names, unsolicited, |response| match response {
Response::MailboxData(MailboxDatum::List { Response::MailboxData(MailboxDatum::List {
flags, name_attributes,
delimiter, delimiter,
name, name,
}) => Ok(MapOrNot::Map(Name { }) => Ok(MapOrNot::Map(Name {
attributes: flags.into_iter().map(NameAttribute::from).collect(), attributes: name_attributes,
delimiter, delimiter,
name, name,
})), })),
@ -73,84 +73,6 @@ pub struct Name<'a> {
pub(crate) name: Cow<'a, str>, pub(crate) name: Cow<'a, str>,
} }
/// An attribute set for an IMAP name.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum NameAttribute<'a> {
/// It is not possible for any child levels of hierarchy to exist
/// under this name; no child levels exist now and none can be
/// created in the future.
NoInferiors,
/// It is not possible to use this name as a selectable mailbox.
NoSelect,
/// The mailbox has been marked "interesting" by the server; the
/// mailbox probably contains messages that have been added since
/// the last time the mailbox was selected.
Marked,
/// The mailbox does not contain any additional messages since the
/// last time the mailbox was selected.
Unmarked,
/// A non-standard user- or server-defined name attribute.
Custom(Cow<'a, str>),
}
impl NameAttribute<'static> {
fn system(s: &str) -> Option<Self> {
match s {
"\\Noinferiors" => Some(NameAttribute::NoInferiors),
"\\Noselect" => Some(NameAttribute::NoSelect),
"\\Marked" => Some(NameAttribute::Marked),
"\\Unmarked" => Some(NameAttribute::Unmarked),
_ => None,
}
}
}
impl<'a> NameAttribute<'a> {
fn into_owned(self) -> NameAttribute<'static> {
match self {
NameAttribute::NoInferiors => NameAttribute::NoInferiors,
NameAttribute::NoSelect => NameAttribute::NoSelect,
NameAttribute::Marked => NameAttribute::Marked,
NameAttribute::Unmarked => NameAttribute::Unmarked,
NameAttribute::Custom(cow) => NameAttribute::Custom(Cow::Owned(cow.into_owned())),
}
}
}
impl<'a> From<String> for NameAttribute<'a> {
fn from(s: String) -> Self {
if let Some(f) = NameAttribute::system(&s) {
f
} else {
NameAttribute::Custom(Cow::Owned(s))
}
}
}
impl<'a> From<Cow<'a, str>> for NameAttribute<'a> {
fn from(s: Cow<'a, str>) -> Self {
if let Some(f) = NameAttribute::system(&*s) {
f
} else {
NameAttribute::Custom(s)
}
}
}
impl<'a> From<&'a str> for NameAttribute<'a> {
fn from(s: &'a str) -> Self {
if let Some(f) = NameAttribute::system(s) {
f
} else {
NameAttribute::Custom(Cow::Borrowed(s))
}
}
}
impl<'a> Name<'a> { impl<'a> Name<'a> {
/// Attributes of this name. /// Attributes of this name.
pub fn attributes(&self) -> &[NameAttribute<'a>] { pub fn attributes(&self) -> &[NameAttribute<'a>] {