mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-22 11:00:19 +00:00
split idle cmd into notify+watch, remove err when empty result
This commit is contained in:
parent
9d40a7d30f
commit
ef1a22d986
6 changed files with 143 additions and 67 deletions
|
@ -28,6 +28,7 @@ pub struct Account {
|
|||
pub downloads_dir: Option<PathBuf>,
|
||||
pub signature: Option<String>,
|
||||
pub default_page_size: Option<usize>,
|
||||
pub watch_cmds: Option<Vec<String>>,
|
||||
|
||||
// Specific
|
||||
pub default: Option<bool>,
|
||||
|
@ -118,10 +119,7 @@ pub struct Config {
|
|||
pub notify_cmd: Option<String>,
|
||||
pub signature: Option<String>,
|
||||
pub default_page_size: Option<usize>,
|
||||
|
||||
#[serde(default)]
|
||||
pub idle_hook_cmds: Vec<String>,
|
||||
|
||||
pub watch_cmds: Option<Vec<String>>,
|
||||
#[serde(flatten)]
|
||||
pub accounts: HashMap<String, Account>,
|
||||
}
|
||||
|
@ -246,15 +244,21 @@ impl Config {
|
|||
.to_owned()
|
||||
}
|
||||
|
||||
pub fn exec_idle_hooks(&self) -> Result<()> {
|
||||
let cmds = self.idle_hook_cmds.to_owned();
|
||||
pub fn exec_watch_cmds(&self, account: &Account) -> Result<()> {
|
||||
let cmds = account
|
||||
.watch_cmds
|
||||
.as_ref()
|
||||
.or_else(|| self.watch_cmds.as_ref())
|
||||
.map(|cmds| cmds.to_owned())
|
||||
.unwrap_or_default();
|
||||
debug!("cmds: {:?}", cmds);
|
||||
|
||||
thread::spawn(move || {
|
||||
debug!("batch execution of {} cmd(s)", cmds.len());
|
||||
cmds.iter().for_each(|cmd| {
|
||||
debug!("execute cmd {:?}", cmd);
|
||||
let res = run_cmd(&cmd);
|
||||
debug!("res: {:?}", res);
|
||||
debug!("running command {:?}…", cmd);
|
||||
let res = run_cmd(cmd);
|
||||
debug!("{:?}", res);
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
@ -12,15 +12,53 @@ error_chain! {
|
|||
}
|
||||
|
||||
pub fn imap_subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
|
||||
vec![clap::SubCommand::with_name("idle").about("Spawns a blocking idle daemon")]
|
||||
vec![
|
||||
clap::SubCommand::with_name("notify")
|
||||
.about("Notifies when new messages arrive in the given mailbox")
|
||||
.aliases(&["idle"])
|
||||
.arg(
|
||||
clap::Arg::with_name("keepalive")
|
||||
.help("Specifies the keepalive duration")
|
||||
.short("k")
|
||||
.long("keepalive")
|
||||
.value_name("SECS")
|
||||
.default_value("500"),
|
||||
),
|
||||
clap::SubCommand::with_name("watch")
|
||||
.about("Watches IMAP server changes")
|
||||
.arg(
|
||||
clap::Arg::with_name("keepalive")
|
||||
.help("Specifies the keepalive duration")
|
||||
.short("k")
|
||||
.long("keepalive")
|
||||
.value_name("SECS")
|
||||
.default_value("500"),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn imap_matches(app: &App) -> Result<bool> {
|
||||
if let Some(_) = app.arg_matches.subcommand_matches("idle") {
|
||||
debug!("idle command matched");
|
||||
if let Some(matches) = app.arg_matches.subcommand_matches("notify") {
|
||||
debug!("notify command matched");
|
||||
|
||||
let keepalive = clap::value_t_or_exit!(matches.value_of("keepalive"), u64);
|
||||
debug!("keepalive: {}", &keepalive);
|
||||
|
||||
let mut imap_conn = ImapConnector::new(&app.account)?;
|
||||
imap_conn.idle(&app.config, &app.mbox)?;
|
||||
imap_conn.notify(&app, keepalive)?;
|
||||
|
||||
imap_conn.logout();
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Some(matches) = app.arg_matches.subcommand_matches("watch") {
|
||||
debug!("watch command matched");
|
||||
|
||||
let keepalive = clap::value_t_or_exit!(matches.value_of("keepalive"), u64);
|
||||
debug!("keepalive: {}", &keepalive);
|
||||
|
||||
let mut imap_conn = ImapConnector::new(&app.account)?;
|
||||
imap_conn.watch(&app, keepalive)?;
|
||||
|
||||
imap_conn.logout();
|
||||
return Ok(true);
|
||||
|
|
|
@ -4,11 +4,7 @@ use log::{debug, trace};
|
|||
use native_tls::{self, TlsConnector, TlsStream};
|
||||
use std::{collections::HashSet, iter::FromIterator, net::TcpStream};
|
||||
|
||||
use crate::{
|
||||
config::model::{Account, Config},
|
||||
flag::model::Flag,
|
||||
msg::model::Msg,
|
||||
};
|
||||
use crate::{app::App, config::model::Account, flag::model::Flag, msg::model::Msg};
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
|
@ -22,30 +18,30 @@ pub struct ImapConnector<'a> {
|
|||
pub sess: imap::Session<TlsStream<TcpStream>>,
|
||||
}
|
||||
|
||||
impl<'ic> ImapConnector<'ic> {
|
||||
pub fn new(account: &'ic Account) -> Result<Self> {
|
||||
impl<'a> ImapConnector<'a> {
|
||||
pub fn new(account: &'a Account) -> Result<Self> {
|
||||
debug!("create TLS builder");
|
||||
let insecure = account.imap_insecure();
|
||||
let tls = TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(insecure)
|
||||
.danger_accept_invalid_hostnames(insecure)
|
||||
.build()
|
||||
.chain_err(|| "Cannot create TLS connector")?;
|
||||
.chain_err(|| "Could not create TLS connector")?;
|
||||
|
||||
debug!("create client");
|
||||
let client = if account.imap_starttls() {
|
||||
imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls)
|
||||
.chain_err(|| "Cannot connect using STARTTLS")
|
||||
.chain_err(|| "Could not connect using STARTTLS")
|
||||
} else {
|
||||
imap::connect(account.imap_addr(), &account.imap_host, &tls)
|
||||
.chain_err(|| "Cannot connect using TLS")
|
||||
.chain_err(|| "Could not connect using TLS")
|
||||
}?;
|
||||
|
||||
debug!("create session");
|
||||
let sess = client
|
||||
.login(&account.imap_login, &account.imap_passwd()?)
|
||||
.map_err(|res| res.0)
|
||||
.chain_err(|| "Cannot login to IMAP server")?;
|
||||
.chain_err(|| "Could not login to IMAP server")?;
|
||||
|
||||
Ok(Self { account, sess })
|
||||
}
|
||||
|
@ -60,11 +56,11 @@ impl<'ic> ImapConnector<'ic> {
|
|||
pub fn set_flags(&mut self, mbox: &str, uid_seq: &str, flags: &str) -> Result<()> {
|
||||
self.sess
|
||||
.select(mbox)
|
||||
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?;
|
||||
.chain_err(|| format!("Could not select mailbox `{}`", mbox))?;
|
||||
|
||||
self.sess
|
||||
.uid_store(uid_seq, format!("FLAGS ({})", flags))
|
||||
.chain_err(|| format!("Cannot set flags `{}`", &flags))?;
|
||||
.chain_err(|| format!("Could not set flags `{}`", &flags))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -72,11 +68,11 @@ impl<'ic> ImapConnector<'ic> {
|
|||
pub fn add_flags(&mut self, mbox: &str, uid_seq: &str, flags: &str) -> Result<()> {
|
||||
self.sess
|
||||
.select(mbox)
|
||||
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?;
|
||||
.chain_err(|| format!("Could not select mailbox `{}`", mbox))?;
|
||||
|
||||
self.sess
|
||||
.uid_store(uid_seq, format!("+FLAGS ({})", flags))
|
||||
.chain_err(|| format!("Cannot add flags `{}`", &flags))?;
|
||||
.chain_err(|| format!("Could not add flags `{}`", &flags))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -84,11 +80,11 @@ impl<'ic> ImapConnector<'ic> {
|
|||
pub fn remove_flags(&mut self, mbox: &str, uid_seq: &str, flags: &str) -> Result<()> {
|
||||
self.sess
|
||||
.select(mbox)
|
||||
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?;
|
||||
.chain_err(|| format!("Could not select mailbox `{}`", mbox))?;
|
||||
|
||||
self.sess
|
||||
.uid_store(uid_seq, format!("-FLAGS ({})", flags))
|
||||
.chain_err(|| format!("Cannot remove flags `{}`", &flags))?;
|
||||
.chain_err(|| format!("Could not remove flags `{}`", &flags))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -106,11 +102,11 @@ impl<'ic> ImapConnector<'ic> {
|
|||
Ok(uids)
|
||||
}
|
||||
|
||||
pub fn idle(&mut self, config: &Config, mbox: &str) -> Result<()> {
|
||||
debug!("examine mailbox: {}", mbox);
|
||||
pub fn notify(&mut self, app: &App, keepalive: u64) -> Result<()> {
|
||||
debug!("examine mailbox: {}", &app.mbox);
|
||||
self.sess
|
||||
.examine(mbox)
|
||||
.chain_err(|| format!("Could not examine mailbox `{}`", mbox))?;
|
||||
.examine(&app.mbox)
|
||||
.chain_err(|| format!("Could not examine mailbox `{}`", &app.mbox))?;
|
||||
|
||||
debug!("init messages hashset");
|
||||
let mut msgs_set: HashSet<u32> =
|
||||
|
@ -122,7 +118,7 @@ impl<'ic> ImapConnector<'ic> {
|
|||
self.sess
|
||||
.idle()
|
||||
.and_then(|mut idle| {
|
||||
idle.set_keepalive(std::time::Duration::new(300, 0));
|
||||
idle.set_keepalive(std::time::Duration::new(keepalive, 0));
|
||||
idle.wait_keepalive()
|
||||
})
|
||||
.chain_err(|| "Could not start the idle mode")?;
|
||||
|
@ -151,7 +147,7 @@ impl<'ic> ImapConnector<'ic> {
|
|||
let uid = fetch.uid.ok_or_else(|| {
|
||||
format!("Could not retrieve message {}'s UID", fetch.message)
|
||||
})?;
|
||||
config.run_notify_cmd(&msg.subject, &msg.sender)?;
|
||||
app.config.run_notify_cmd(&msg.subject, &msg.sender)?;
|
||||
debug!("notify message: {}", uid);
|
||||
trace!("message: {:?}", msg);
|
||||
|
||||
|
@ -161,7 +157,26 @@ impl<'ic> ImapConnector<'ic> {
|
|||
}
|
||||
}
|
||||
|
||||
config.exec_idle_hooks()?;
|
||||
debug!("end loop");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn watch(&mut self, app: &App, keepalive: u64) -> Result<()> {
|
||||
debug!("examine mailbox: {}", &app.mbox);
|
||||
self.sess
|
||||
.examine(&app.mbox)
|
||||
.chain_err(|| format!("Could not examine mailbox `{}`", &app.mbox))?;
|
||||
|
||||
loop {
|
||||
debug!("begin loop");
|
||||
self.sess
|
||||
.idle()
|
||||
.and_then(|mut idle| {
|
||||
idle.set_keepalive(std::time::Duration::new(keepalive, 0));
|
||||
idle.wait_keepalive()
|
||||
})
|
||||
.chain_err(|| "Could not start the idle mode")?;
|
||||
app.config.exec_watch_cmds(&app.account)?;
|
||||
debug!("end loop");
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +185,7 @@ impl<'ic> ImapConnector<'ic> {
|
|||
let names = self
|
||||
.sess
|
||||
.list(Some(""), Some("*"))
|
||||
.chain_err(|| "Cannot list mailboxes")?;
|
||||
.chain_err(|| "Could not list mailboxes")?;
|
||||
|
||||
Ok(names)
|
||||
}
|
||||
|
@ -180,15 +195,15 @@ impl<'ic> ImapConnector<'ic> {
|
|||
mbox: &str,
|
||||
page_size: &usize,
|
||||
page: &usize,
|
||||
) -> Result<imap::types::ZeroCopy<Vec<imap::types::Fetch>>> {
|
||||
) -> Result<Option<imap::types::ZeroCopy<Vec<imap::types::Fetch>>>> {
|
||||
let last_seq = self
|
||||
.sess
|
||||
.select(mbox)
|
||||
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?
|
||||
.chain_err(|| format!("Could not select mailbox `{}`", mbox))?
|
||||
.exists as i64;
|
||||
|
||||
if last_seq == 0 {
|
||||
return Err(format!("The `{}` mailbox is empty", mbox).into());
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// TODO: add tests, improve error management when empty page
|
||||
|
@ -204,9 +219,9 @@ impl<'ic> ImapConnector<'ic> {
|
|||
let fetches = self
|
||||
.sess
|
||||
.fetch(range, "(UID FLAGS ENVELOPE INTERNALDATE)")
|
||||
.chain_err(|| "Cannot fetch messages")?;
|
||||
.chain_err(|| "Could not fetch messages")?;
|
||||
|
||||
Ok(fetches)
|
||||
Ok(Some(fetches))
|
||||
}
|
||||
|
||||
pub fn search_msgs(
|
||||
|
@ -215,45 +230,46 @@ impl<'ic> ImapConnector<'ic> {
|
|||
query: &str,
|
||||
page_size: &usize,
|
||||
page: &usize,
|
||||
) -> Result<imap::types::ZeroCopy<Vec<imap::types::Fetch>>> {
|
||||
) -> Result<Option<imap::types::ZeroCopy<Vec<imap::types::Fetch>>>> {
|
||||
self.sess
|
||||
.select(mbox)
|
||||
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?;
|
||||
.chain_err(|| format!("Could not select mailbox `{}`", mbox))?;
|
||||
|
||||
let begin = page * page_size;
|
||||
let end = begin + (page_size - 1);
|
||||
let uids = self
|
||||
let uids: Vec<String> = self
|
||||
.sess
|
||||
.search(query)
|
||||
.chain_err(|| format!("Cannot search in `{}` with query `{}`", mbox, query))?
|
||||
.chain_err(|| format!("Could not search in `{}` with query `{}`", mbox, query))?
|
||||
.iter()
|
||||
.map(|seq| seq.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let range = uids[begin..end.min(uids.len())].join(",");
|
||||
.collect();
|
||||
|
||||
if uids.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let range = uids[begin..end.min(uids.len())].join(",");
|
||||
let fetches = self
|
||||
.sess
|
||||
.fetch(&range, "(UID FLAGS ENVELOPE INTERNALDATE)")
|
||||
.chain_err(|| format!("Cannot fetch range `{}`", &range))?;
|
||||
// .iter()
|
||||
// .map(|fetch| Msg::from(fetch))
|
||||
// .collect::<Vec<_>>();
|
||||
.chain_err(|| format!("Could not fetch range `{}`", &range))?;
|
||||
|
||||
Ok(fetches)
|
||||
Ok(Some(fetches))
|
||||
}
|
||||
|
||||
pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result<Vec<u8>> {
|
||||
self.sess
|
||||
.select(mbox)
|
||||
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?;
|
||||
.chain_err(|| format!("Could not select mailbox `{}`", mbox))?;
|
||||
|
||||
match self
|
||||
.sess
|
||||
.uid_fetch(uid, "(FLAGS BODY[])")
|
||||
.chain_err(|| "Cannot fetch bodies")?
|
||||
.chain_err(|| "Could not fetch bodies")?
|
||||
.first()
|
||||
{
|
||||
None => Err(format!("Cannot find message `{}`", uid).into()),
|
||||
None => Err(format!("Could not find message `{}`", uid).into()),
|
||||
Some(fetch) => Ok(fetch.body().unwrap_or(&[]).to_vec()),
|
||||
}
|
||||
}
|
||||
|
@ -261,7 +277,7 @@ impl<'ic> ImapConnector<'ic> {
|
|||
pub fn append_msg(&mut self, mbox: &str, msg: &[u8], flags: &[Flag]) -> Result<()> {
|
||||
self.sess
|
||||
.append_with_flags(mbox, msg, flags)
|
||||
.chain_err(|| format!("Cannot append message to `{}`", mbox))?;
|
||||
.chain_err(|| format!("Could not append message to `{}`", mbox))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -187,7 +187,12 @@ pub fn msg_matches(app: &App) -> Result<bool> {
|
|||
|
||||
let mut imap_conn = ImapConnector::new(&app.account)?;
|
||||
let msgs = imap_conn.list_msgs(&app.mbox, &page_size, &page)?;
|
||||
let msgs = Msgs::from(&msgs);
|
||||
let msgs = if let Some(ref fetches) = msgs {
|
||||
Msgs::from(fetches)
|
||||
} else {
|
||||
Msgs::new()
|
||||
};
|
||||
|
||||
trace!("messages: {:?}", msgs);
|
||||
app.output.print(msgs);
|
||||
|
||||
|
@ -238,7 +243,11 @@ pub fn msg_matches(app: &App) -> Result<bool> {
|
|||
|
||||
let mut imap_conn = ImapConnector::new(&app.account)?;
|
||||
let msgs = imap_conn.search_msgs(&app.mbox, &query, &page_size, &page)?;
|
||||
let msgs = Msgs::from(&msgs);
|
||||
let msgs = if let Some(ref fetches) = msgs {
|
||||
Msgs::from(fetches)
|
||||
} else {
|
||||
Msgs::new()
|
||||
};
|
||||
trace!("messages: {:?}", msgs);
|
||||
app.output.print(msgs);
|
||||
|
||||
|
@ -637,7 +646,11 @@ pub fn msg_matches(app: &App) -> Result<bool> {
|
|||
let mut imap_conn = ImapConnector::new(&app.account)?;
|
||||
let msgs =
|
||||
imap_conn.list_msgs(&app.mbox, &app.config.default_page_size(&app.account), &0)?;
|
||||
let msgs = Msgs::from(&msgs);
|
||||
let msgs = if let Some(ref fetches) = msgs {
|
||||
Msgs::from(fetches)
|
||||
} else {
|
||||
Msgs::new()
|
||||
};
|
||||
app.output.print(msgs);
|
||||
|
||||
imap_conn.logout();
|
||||
|
|
|
@ -80,7 +80,6 @@ impl<'a> Attachments {
|
|||
.get_headers()
|
||||
.get_first_value("content-type")
|
||||
.unwrap_or_default();
|
||||
|
||||
if !ctype.starts_with("text") {
|
||||
self.0.push(Attachment::from_part(part));
|
||||
}
|
||||
|
@ -646,15 +645,21 @@ impl<'m> Table for Msg<'m> {
|
|||
// Msgs
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Msgs<'m>(pub Vec<Msg<'m>>);
|
||||
pub struct Msgs<'a>(pub Vec<Msg<'a>>);
|
||||
|
||||
impl<'m> From<&'m imap::types::ZeroCopy<Vec<imap::types::Fetch>>> for Msgs<'m> {
|
||||
fn from(fetches: &'m imap::types::ZeroCopy<Vec<imap::types::Fetch>>) -> Self {
|
||||
impl<'a> From<&'a imap::types::ZeroCopy<Vec<imap::types::Fetch>>> for Msgs<'a> {
|
||||
fn from(fetches: &'a imap::types::ZeroCopy<Vec<imap::types::Fetch>>) -> Self {
|
||||
Self(fetches.iter().rev().map(Msg::from).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'m> fmt::Display for Msgs<'m> {
|
||||
impl Msgs<'_> {
|
||||
pub fn new() -> Self {
|
||||
Self(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Msgs<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "\n{}", Table::render(&self.0))
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ where
|
|||
fn render(items: &[Self]) -> String {
|
||||
Self::build(items)
|
||||
.iter()
|
||||
.map(|row| row.join(&Cell::new("|").ext(8).to_string()))
|
||||
.map(|row| row.join(&Cell::new("│").ext(8).to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue