mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-17 05:07:55 +08:00
add idle mode
This commit is contained in:
+28
-9
@@ -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
@@ -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
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user