diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad4b175..1d6bd360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Save msg upon error [#59] - Answered flag not set [#50] - Panic when downloads-dir does not exist [#100] +- Idle mode incorrect new message notification [#48] ### Changed @@ -156,6 +157,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#39]: https://github.com/soywod/himalaya/issues/39 [#40]: https://github.com/soywod/himalaya/issues/40 [#41]: https://github.com/soywod/himalaya/issues/41 +[#48]: https://github.com/soywod/himalaya/issues/48 [#50]: https://github.com/soywod/himalaya/issues/50 [#58]: https://github.com/soywod/himalaya/issues/58 [#59]: https://github.com/soywod/himalaya/issues/59 diff --git a/src/imap/cli.rs b/src/imap/cli.rs index 09c5ac85..85ef1d24 100644 --- a/src/imap/cli.rs +++ b/src/imap/cli.rs @@ -1,5 +1,6 @@ use clap::{self, App, ArgMatches, SubCommand}; use error_chain::error_chain; +use log::debug; use crate::{config::model::Config, imap::model::ImapConnector}; @@ -20,6 +21,7 @@ pub fn imap_matches(matches: &ArgMatches) -> Result { let mbox = matches.value_of("mailbox").unwrap(); if let Some(_) = matches.subcommand_matches("idle") { + debug!("[imap::cli] idle command matched"); let mut imap_conn = ImapConnector::new(&account)?; imap_conn.idle(&config, &mbox)?; imap_conn.logout(); diff --git a/src/imap/model.rs b/src/imap/model.rs index 33876a4c..1cb9b307 100644 --- a/src/imap/model.rs +++ b/src/imap/model.rs @@ -1,7 +1,8 @@ use error_chain::error_chain; use imap; +use log::{debug, trace}; use native_tls::{self, TlsConnector, TlsStream}; -use std::net::TcpStream; +use std::{collections::HashSet, iter::FromIterator, net::TcpStream}; use crate::{ config::model::{Account, Config}, @@ -25,10 +26,10 @@ pub struct ImapConnector<'a> { impl<'ic> ImapConnector<'ic> { pub fn new(account: &'ic Account) -> Result { let tls = TlsConnector::builder() - .danger_accept_invalid_certs(account.imap_insecure()) - .danger_accept_invalid_hostnames(account.imap_insecure()) - .build() - .chain_err(|| "Cannot create TLS connector")?; + .danger_accept_invalid_certs(account.imap_insecure()) + .danger_accept_invalid_hostnames(account.imap_insecure()) + .build() + .chain_err(|| "Cannot create TLS connector")?; let client = if account.imap_starttls() { imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls) @@ -37,6 +38,7 @@ impl<'ic> ImapConnector<'ic> { imap::connect(account.imap_addr(), &account.imap_host, &tls) .chain_err(|| "Cannot connect using TLS") }?; + let sess = client .login(&account.imap_login, &account.imap_passwd()?) .map_err(|res| res.0) @@ -87,43 +89,82 @@ impl<'ic> ImapConnector<'ic> { Ok(()) } - fn last_new_seq(&mut self) -> Result> { - Ok(self + fn search_new_msgs(&mut self) -> Result> { + debug!("[imap::model::search_new_msgs] begin"); + + let seqs: Vec = self .sess - .uid_search("NEW") - .chain_err(|| "Cannot search new uids")? + .search("NEW") + .chain_err(|| "Could not search new messages")? .into_iter() - .next()) + .collect(); + debug!( + "[imap::model::search_new_msgs] found {} new messages", + seqs.len() + ); + trace!("[imap::model::search_new_msgs] {:?}", seqs); + + Ok(seqs) } pub fn idle(&mut self, config: &Config, mbox: &str) -> Result<()> { - let mut prev_seq = 0; + debug!("[imap::model::idle] begin"); + + debug!("[imap::model::idle] examine mailbox {}", mbox); self.sess .examine(mbox) - .chain_err(|| format!("Cannot examine mailbox `{}`", mbox))?; + .chain_err(|| format!("Could not examine mailbox `{}`", mbox))?; + + debug!("[imap::model::idle] init message hashset"); + let mut msg_set: HashSet = HashSet::from_iter(self.search_new_msgs()?.iter().cloned()); + trace!("[imap::model::idle] {:?}", msg_set); loop { + debug!("[imap::model::idle] begin loop"); + self.sess .idle() .and_then(|idle| idle.wait_keepalive()) - .chain_err(|| "Cannot wait in IDLE mode")?; + .chain_err(|| "Could not enter in idle mode")?; - if let Some(seq) = self.last_new_seq()? { - if prev_seq != seq { - let msgs = self - .sess - .uid_fetch(seq.to_string(), "(ENVELOPE)") - .chain_err(|| "Cannot fetch enveloppe")?; - let msg = msgs - .iter() - .next() - .ok_or_else(|| "Cannot fetch first message") - .map(Msg::from)?; + let new_msgs: Vec = self + .search_new_msgs()? + .into_iter() + .filter(|seq| msg_set.get(&seq).is_none()) + .collect(); + debug!( + "[imap::model::idle] found {} new messages not in hashset", + new_msgs.len() + ); + trace!("[imap::model::idle] {:?}", new_msgs); + if !new_msgs.is_empty() { + let new_msgs = new_msgs + .iter() + .map(|seq| seq.to_string()) + .collect::>() + .join(","); + let fetches = self + .sess + .fetch(new_msgs, "(ENVELOPE)") + .chain_err(|| "Cannot fetch new messages enveloppe")?; + + for fetch in fetches.iter() { + let msg = Msg::from(fetch); config.run_notify_cmd(&msg.subject, &msg.sender)?; - prev_seq = seq; + debug!("[imap::model::idle] notify message {}", fetch.message); + trace!("[imap::model::idle] {:?}", msg); + + debug!( + "[imap::model::idle] insert msg {} to hashset", + fetch.message + ); + msg_set.insert(fetch.message); + trace!("[imap::model::idle] {:?}", msg_set); } } + + debug!("[imap::model::idle] end loop"); } } diff --git a/src/main.rs b/src/main.rs index 4bfe70f9..ec2b4a41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,14 +78,14 @@ fn build_cli() -> App<'static, 'static> { fn run() -> Result<()> { let matches = build_cli().get_matches(); - let output_fmt: OutputFmt = matches.value_of("output").unwrap().into(); let log_level: LogLevel = matches.value_of("log").unwrap().into(); - init_logger(&output_fmt, &log_level)?; - debug!("Logger initialized"); - debug!("Output format: {}", &output_fmt); - debug!("Log level: {}", &log_level); + + debug!("[main] begin"); + debug!("[main] output format: {}", &output_fmt); + debug!("[main] log level: {}", &log_level); + debug!("[main] browse matches"); loop { if mbox_matches(&matches)? { diff --git a/src/smtp.rs b/src/smtp.rs index c1ac0f97..fa2ed509 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -26,16 +26,16 @@ pub fn send(account: &Account, msg: &lettre::Message) -> Result<()> { let tls = TlsParameters::builder(account.smtp_host.to_string()) .dangerous_accept_invalid_hostnames(account.smtp_insecure()) .dangerous_accept_invalid_certs(account.smtp_insecure()) - .build() - .unwrap(); + .build()?; + let tls = if account.smtp_starttls() { + Tls::Required(tls) + } else { + Tls::Wrapper(tls) + }; smtp_relay(&account.smtp_host)? .port(account.smtp_port) - .tls(if account.smtp_starttls() { - Tls::Required(tls) - } else { - Tls::Wrapper(tls) - }) + .tls(tls) .credentials(account.smtp_creds()?) .build() .send(msg)?;