Skip to content

Commit

Permalink
add NOTIFY handling
Browse files Browse the repository at this point in the history
  • Loading branch information
jkaessens committed Jan 18, 2018
1 parent 5d19d4f commit f6b6c96
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 1 deletion.
98 changes: 97 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ use std::time::Duration;
use bufstream::BufStream;
use nom::IResult;

use std::collections::HashMap;
use super::types::*;
use super::authenticator::Authenticator;
use super::parse::{parse_authenticate_response, parse_capabilities, parse_fetches, parse_mailbox,
parse_names};
parse_names, parse_notify_status};
use super::error::{Error, ParseError, Result, ValidateError};

static TAG_PREFIX: &'static str = "a";
Expand Down Expand Up @@ -54,6 +55,43 @@ pub struct IdleHandle<'a, T: Read + Write + 'a> {
done: bool,
}

pub struct NotifyItem<'a> {
mailbox: &'a str,
events: &'a[&'a str],
}

pub struct NotifyOp<'a> {
items: Vec<NotifyItem<'a>>,
}

impl<'a> NotifyOp<'a> {
pub fn new() -> Self {
NotifyOp {
items: vec![],
}
}

pub fn add_mailbox(mut self, mailbox_spec: &'a str, events: &'a[&'a str]) -> NotifyOp<'a> {
self.items.push(NotifyItem {
mailbox: mailbox_spec,
events: events,
});
self
}

pub fn is_none(&self) -> bool {
self.items.is_empty()
}
}


#[derive(Debug)]
pub struct NotifyHandle<'a, T: Read + Write + 'a> {
client: &'a mut Client<T>,
keepalive: Duration,
}


/// Must be implemented for a transport in order for a `Client` using that transport to support
/// operations with timeouts.
///
Expand All @@ -68,6 +106,59 @@ pub trait SetReadTimeout {
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()>;
}

impl<'a, T: Read + Write + 'a> NotifyHandle<'a, T> {
fn new(client: &'a mut Client<T>) -> Result<Self> {
Ok(NotifyHandle {
client: client,
keepalive: Duration::from_secs(29 * 60),
})
}

fn set(&mut self, cmd: NotifyOp<'a>) -> Result<()> {
// This command will end in a tagged OK but if the NOTIFY args contains
// a `STATUS` identifier, one status line for each specified mailbox
// will be emitted before the final OK.
// self.client.run_command_and_read_response(&format!("NOTIFY {}"))

let mut s = "NOTIFY ".to_owned();

if cmd.items.len() == 0 {
s += "NONE";
} else {
s += "SET ";
for i in cmd.items.iter() {
s += &format!("({} ({}))",
i.mailbox,
i.events.iter().fold(String::new(), |sofar, cur| format!("{} {}", sofar, cur)));
}
};

self.client.run_command_and_check_ok(&s)
}

fn set_status(&mut self, cmd: NotifyOp<'a>) -> Result<HashMap<String, Mailbox>> {
if cmd.items.len() == 0 {
panic!("Cannot request a STATUS response without a mailbox specification!");
}

let mut s = "NOTIFY ".to_owned();

if cmd.items.len() == 0 {
s += "NONE";
} else {
s += "STATUS SET ";
for i in cmd.items.iter() {
s += &format!("({} ({}))",
i.mailbox,
i.events.iter().fold(String::new(), |sofar, cur| format!("{} {}", sofar, cur)));
}
};

let response: Vec<u8> = self.client.run_command_and_read_response(&s)?;
parse_notify_status(&response)
}
}

impl<'a, T: Read + Write + 'a> IdleHandle<'a, T> {
fn new(client: &'a mut Client<T>) -> Result<Self> {
let mut h = IdleHandle {
Expand Down Expand Up @@ -445,6 +536,11 @@ impl<T: Read + Write> Client<T> {
IdleHandle::new(self)
}

/// Returns a handle that can be used to issue NOTIFY requests to a server that supports it
pub fn notify(&mut self) -> Result<NotifyHandle<T>> {
NotifyHandle::new(self)
}

/// The APPEND command adds a mail to a mailbox.
pub fn append(&mut self, folder: &str, content: &[u8]) -> Result<()> {
try!(self.run_command(
Expand Down
50 changes: 50 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use regex::Regex;
use nom::IResult;
use imap_proto::{self, Response};
use imap_proto::types::StatusAttribute;
use std::collections::HashMap;

use super::types::*;
use super::error::{Error, ParseError, Result};
Expand Down Expand Up @@ -133,6 +134,55 @@ pub fn parse_capabilities(lines: Vec<u8>) -> ZeroCopyResult<Capabilities> {
unsafe { ZeroCopy::new(lines, f) }
}

pub fn parse_notify_status(mut lines: &[u8]) -> Result<HashMap<String, Mailbox>> {
let mut mailboxes = HashMap::new();
loop {

match imap_proto::parse_response(&lines) {
// Response contains data relating to a mailbox, exactly one mailbox per line
IResult::Done(rest, Response::MailboxData(m)) => {
lines = rest;

use imap_proto::MailboxDatum;
match m {
MailboxDatum::Status { mailbox: mailbox_name, status } => {
let mut mailbox = Mailbox::default();

for f in status.into_iter() {
match f {
StatusAttribute::Recent(r) => mailbox.recent = r,
StatusAttribute::Unseen(u) => mailbox.unseen = Some(u),
StatusAttribute::UidNext(u) => mailbox.uid_next = Some(u),
StatusAttribute::UidValidity(u) => mailbox.uid_validity = Some(u),
StatusAttribute::Messages(m) => mailbox.exists = m,
}
}

mailboxes.insert(mailbox_name.to_owned(), mailbox);
if lines.is_empty() {
break Ok(mailboxes)
}
},
_ => {}
}
},
IResult::Done(rest, Response::Data { ref status, ..}) if *status == imap_proto::Status::No => {
// It is okay to get a NO response in a data line. This happens when the IMAP server
// iterates over the mail folder and finds subfolders that are not mailboxes. This is
// not a client error but a server misconfiguration and happens only to single mailboxes.
// Other lines may contain valid mailbox statuses, so be a little more reluctant here.
lines = rest;
}
IResult::Done(_, resp) => {
break Err(resp.into());
}
_ => {
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
}
}
}
}

pub fn parse_mailbox(mut lines: &[u8]) -> Result<Mailbox> {
let mut mailbox = Mailbox::default();

Expand Down

0 comments on commit f6b6c96

Please sign in to comment.