fix idle mode notifications (#48)

This commit is contained in:
Clément DOUIN 2021-04-17 00:00:52 +02:00
parent 1c6f249ca4
commit f3a67cecd5
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
5 changed files with 82 additions and 37 deletions

View file

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Save msg upon error [#59] - Save msg upon error [#59]
- Answered flag not set [#50] - Answered flag not set [#50]
- Panic when downloads-dir does not exist [#100] - Panic when downloads-dir does not exist [#100]
- Idle mode incorrect new message notification [#48]
### Changed ### 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 [#39]: https://github.com/soywod/himalaya/issues/39
[#40]: https://github.com/soywod/himalaya/issues/40 [#40]: https://github.com/soywod/himalaya/issues/40
[#41]: https://github.com/soywod/himalaya/issues/41 [#41]: https://github.com/soywod/himalaya/issues/41
[#48]: https://github.com/soywod/himalaya/issues/48
[#50]: https://github.com/soywod/himalaya/issues/50 [#50]: https://github.com/soywod/himalaya/issues/50
[#58]: https://github.com/soywod/himalaya/issues/58 [#58]: https://github.com/soywod/himalaya/issues/58
[#59]: https://github.com/soywod/himalaya/issues/59 [#59]: https://github.com/soywod/himalaya/issues/59

View file

@ -1,5 +1,6 @@
use clap::{self, App, ArgMatches, SubCommand}; use clap::{self, App, ArgMatches, SubCommand};
use error_chain::error_chain; use error_chain::error_chain;
use log::debug;
use crate::{config::model::Config, imap::model::ImapConnector}; use crate::{config::model::Config, imap::model::ImapConnector};
@ -20,6 +21,7 @@ pub fn imap_matches(matches: &ArgMatches) -> Result<bool> {
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
if let Some(_) = matches.subcommand_matches("idle") { if let Some(_) = matches.subcommand_matches("idle") {
debug!("[imap::cli] idle command matched");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.idle(&config, &mbox)?; imap_conn.idle(&config, &mbox)?;
imap_conn.logout(); imap_conn.logout();

View file

@ -1,7 +1,8 @@
use error_chain::error_chain; use error_chain::error_chain;
use imap; use imap;
use log::{debug, trace};
use native_tls::{self, TlsConnector, TlsStream}; use native_tls::{self, TlsConnector, TlsStream};
use std::net::TcpStream; use std::{collections::HashSet, iter::FromIterator, net::TcpStream};
use crate::{ use crate::{
config::model::{Account, Config}, config::model::{Account, Config},
@ -25,10 +26,10 @@ pub struct ImapConnector<'a> {
impl<'ic> ImapConnector<'ic> { impl<'ic> ImapConnector<'ic> {
pub fn new(account: &'ic Account) -> Result<Self> { pub fn new(account: &'ic Account) -> Result<Self> {
let tls = TlsConnector::builder() let tls = TlsConnector::builder()
.danger_accept_invalid_certs(account.imap_insecure()) .danger_accept_invalid_certs(account.imap_insecure())
.danger_accept_invalid_hostnames(account.imap_insecure()) .danger_accept_invalid_hostnames(account.imap_insecure())
.build() .build()
.chain_err(|| "Cannot create TLS connector")?; .chain_err(|| "Cannot create TLS connector")?;
let client = if account.imap_starttls() { let client = if account.imap_starttls() {
imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls) 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) imap::connect(account.imap_addr(), &account.imap_host, &tls)
.chain_err(|| "Cannot connect using TLS") .chain_err(|| "Cannot connect using TLS")
}?; }?;
let sess = client let sess = client
.login(&account.imap_login, &account.imap_passwd()?) .login(&account.imap_login, &account.imap_passwd()?)
.map_err(|res| res.0) .map_err(|res| res.0)
@ -87,43 +89,82 @@ impl<'ic> ImapConnector<'ic> {
Ok(()) Ok(())
} }
fn last_new_seq(&mut self) -> Result<Option<u32>> { fn search_new_msgs(&mut self) -> Result<Vec<u32>> {
Ok(self debug!("[imap::model::search_new_msgs] begin");
let seqs: Vec<u32> = self
.sess .sess
.uid_search("NEW") .search("NEW")
.chain_err(|| "Cannot search new uids")? .chain_err(|| "Could not search new messages")?
.into_iter() .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<()> { 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 self.sess
.examine(mbox) .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<u32> = HashSet::from_iter(self.search_new_msgs()?.iter().cloned());
trace!("[imap::model::idle] {:?}", msg_set);
loop { loop {
debug!("[imap::model::idle] begin loop");
self.sess self.sess
.idle() .idle()
.and_then(|idle| idle.wait_keepalive()) .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()? { let new_msgs: Vec<u32> = self
if prev_seq != seq { .search_new_msgs()?
let msgs = self .into_iter()
.sess .filter(|seq| msg_set.get(&seq).is_none())
.uid_fetch(seq.to_string(), "(ENVELOPE)") .collect();
.chain_err(|| "Cannot fetch enveloppe")?; debug!(
let msg = msgs "[imap::model::idle] found {} new messages not in hashset",
.iter() new_msgs.len()
.next() );
.ok_or_else(|| "Cannot fetch first message") trace!("[imap::model::idle] {:?}", new_msgs);
.map(Msg::from)?;
if !new_msgs.is_empty() {
let new_msgs = new_msgs
.iter()
.map(|seq| seq.to_string())
.collect::<Vec<_>>()
.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)?; 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");
} }
} }

View file

@ -78,14 +78,14 @@ fn build_cli() -> App<'static, 'static> {
fn run() -> Result<()> { fn run() -> Result<()> {
let matches = build_cli().get_matches(); let matches = build_cli().get_matches();
let output_fmt: OutputFmt = matches.value_of("output").unwrap().into(); let output_fmt: OutputFmt = matches.value_of("output").unwrap().into();
let log_level: LogLevel = matches.value_of("log").unwrap().into(); let log_level: LogLevel = matches.value_of("log").unwrap().into();
init_logger(&output_fmt, &log_level)?; init_logger(&output_fmt, &log_level)?;
debug!("Logger initialized");
debug!("Output format: {}", &output_fmt); debug!("[main] begin");
debug!("Log level: {}", &log_level); debug!("[main] output format: {}", &output_fmt);
debug!("[main] log level: {}", &log_level);
debug!("[main] browse matches");
loop { loop {
if mbox_matches(&matches)? { if mbox_matches(&matches)? {

View file

@ -26,16 +26,16 @@ pub fn send(account: &Account, msg: &lettre::Message) -> Result<()> {
let tls = TlsParameters::builder(account.smtp_host.to_string()) let tls = TlsParameters::builder(account.smtp_host.to_string())
.dangerous_accept_invalid_hostnames(account.smtp_insecure()) .dangerous_accept_invalid_hostnames(account.smtp_insecure())
.dangerous_accept_invalid_certs(account.smtp_insecure()) .dangerous_accept_invalid_certs(account.smtp_insecure())
.build() .build()?;
.unwrap(); let tls = if account.smtp_starttls() {
Tls::Required(tls)
} else {
Tls::Wrapper(tls)
};
smtp_relay(&account.smtp_host)? smtp_relay(&account.smtp_host)?
.port(account.smtp_port) .port(account.smtp_port)
.tls(if account.smtp_starttls() { .tls(tls)
Tls::Required(tls)
} else {
Tls::Wrapper(tls)
})
.credentials(account.smtp_creds()?) .credentials(account.smtp_creds()?)
.build() .build()
.send(msg)?; .send(msg)?;