add idle mode

This commit is contained in:
Clément DOUIN
2021-03-11 17:05:01 +01:00
parent b2f1543bbf
commit 6e9e7cd30e
5 changed files with 152 additions and 84 deletions
+28 -9
View File
@@ -24,21 +24,28 @@ pub enum Error {
GetAccountNotFoundError(String),
GetAccountDefaultNotFoundError,
OutputError(output::Error),
// new erorrs,
RunNotifyCmdError(output::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "config: ")?;
use Error::*;
match self {
Error::IoError(err) => err.fmt(f),
Error::ParseTomlError(err) => err.fmt(f),
Error::ParseTomlAccountsError => write!(f, "no account found"),
Error::GetEnvVarError(err) => err.fmt(f),
Error::GetPathNotFoundError => write!(f, "path not found"),
Error::GetAccountNotFoundError(account) => write!(f, "account {} not found", account),
Error::GetAccountDefaultNotFoundError => write!(f, "no default account found"),
Error::OutputError(err) => err.fmt(f),
IoError(err) => err.fmt(f),
ParseTomlError(err) => err.fmt(f),
ParseTomlAccountsError => write!(f, "no account found"),
GetEnvVarError(err) => err.fmt(f),
GetPathNotFoundError => write!(f, "path not found"),
GetAccountNotFoundError(account) => write!(f, "account {} not found", account),
GetAccountDefaultNotFoundError => write!(f, "no default account found"),
OutputError(err) => err.fmt(f),
RunNotifyCmdError(err) => {
write!(f, "run notification cmd: ")?;
err.fmt(f)
}
}
}
}
@@ -124,6 +131,7 @@ impl Account {
pub struct Config {
pub name: String,
pub downloads_dir: Option<PathBuf>,
pub notification_cmd: Option<String>,
#[serde(flatten)]
pub accounts: HashMap<String, Account>,
@@ -202,4 +210,15 @@ impl Config {
let name = account.name.as_ref().unwrap_or(&self.name);
format!("{} <{}>", name, account.email)
}
pub fn run_notify_cmd(&self, subject: &str, sender: &str) -> Result<()> {
let default_cmd = format!(r#"notify-send "📫 {}" "{}""#, sender, subject);
let cmd = self
.notification_cmd
.as_ref()
.map(|s| format!(r#"{} "{}" "{}""#, s, subject, sender))
.unwrap_or(default_cmd);
run_cmd(&cmd).map_err(Error::RunNotifyCmdError)?;
Ok(())
}
}
+51 -11
View File
@@ -2,9 +2,11 @@ use imap;
use native_tls::{self, TlsConnector, TlsStream};
use std::{fmt, net::TcpStream, result};
use crate::config::{self, Account};
use crate::mbox::{Mbox, Mboxes};
use crate::msg::{Msg, Msgs};
use crate::{
config::{self, Account, Config},
mbox::{Mbox, Mboxes},
msg::{Msg, Msgs},
};
// Error wrapper
@@ -17,26 +19,33 @@ pub enum Error {
ReadEmailEmptyPartError(String, String),
ExtractAttachmentsEmptyError(String),
ConfigError(config::Error),
// new errors
IdleError(imap::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "imap: ")?;
use Error::*;
match self {
Error::CreateTlsConnectorError(err) => err.fmt(f),
Error::CreateImapSession(err) => err.fmt(f),
Error::ParseEmailError(err) => err.fmt(f),
Error::ConfigError(err) => err.fmt(f),
Error::ReadEmailNotFoundError(uid) => {
CreateTlsConnectorError(err) => err.fmt(f),
CreateImapSession(err) => err.fmt(f),
ParseEmailError(err) => err.fmt(f),
ConfigError(err) => err.fmt(f),
ReadEmailNotFoundError(uid) => {
write!(f, "no email found for uid {}", uid)
}
Error::ReadEmailEmptyPartError(uid, mime) => {
ReadEmailEmptyPartError(uid, mime) => {
write!(f, "no {} content found for uid {}", mime, uid)
}
Error::ExtractAttachmentsEmptyError(uid) => {
ExtractAttachmentsEmptyError(uid) => {
write!(f, "no attachment found for uid {}", uid)
}
IdleError(err) => {
write!(f, "IMAP idle mode: ")?;
err.fmt(f)
}
}
}
}
@@ -97,6 +106,37 @@ impl<'a> ImapConnector<'a> {
}
}
fn last_new_seq(&mut self) -> Result<Option<u32>> {
Ok(self.sess.uid_search("NEW")?.into_iter().next())
}
pub fn idle(&mut self, config: &Config, mbox: &str) -> Result<()> {
let mut prev_seq = 0;
self.sess.examine(mbox)?;
loop {
self.sess
.idle()
.and_then(|idle| idle.wait_keepalive())
.map_err(Error::IdleError)?;
if let Some(seq) = self.last_new_seq()? {
if prev_seq != seq {
if let Some(msg) = self
.sess
.uid_fetch(seq.to_string(), "(ENVELOPE)")?
.iter()
.next()
.map(Msg::from)
{
config.run_notify_cmd(&msg.subject, &msg.sender)?;
prev_seq = seq;
}
}
}
}
}
pub fn list_mboxes(&mut self) -> Result<Mboxes> {
let mboxes = self
.sess
+13
View File
@@ -253,6 +253,11 @@ fn run() -> Result<()> {
.arg(mailbox_arg()),
),
)
.subcommand(
SubCommand::with_name("idle")
.about("Starts the idle mode")
.arg(mailbox_arg()),
)
.get_matches();
let account_name = matches.value_of("account");
@@ -525,6 +530,14 @@ fn run() -> Result<()> {
imap_conn.logout();
}
if let Some(matches) = matches.subcommand_matches("idle") {
let config = Config::new_from_file()?;
let account = config.find_account_by_name(account_name)?;
let mut imap_conn = ImapConnector::new(&account)?;
let mbox = matches.value_of("mailbox").unwrap();
imap_conn.idle(&config, &mbox)?;
}
Ok(())
}